start making sql async

This commit is contained in:
dignifiedquire
2020-03-07 18:54:09 +01:00
parent 7326ba1403
commit 6ea1d665bb
27 changed files with 3057 additions and 2421 deletions

View File

@@ -49,7 +49,7 @@ async fn main() {
let ctx1 = ctx.clone(); let ctx1 = ctx.clone();
let t1 = async_std::task::spawn(async move { let t1 = async_std::task::spawn(async move {
while *r1.read().await { while *r1.read().await {
// perform_inbox_jobs(&ctx1).await; perform_inbox_jobs(&ctx1).await;
if *r1.read().await { if *r1.read().await {
perform_inbox_fetch(&ctx1).await; perform_inbox_fetch(&ctx1).await;

File diff suppressed because it is too large Load Diff

View File

@@ -85,7 +85,7 @@ impl Chatlist {
/// are returned. /// are returned.
/// `query_contact_id`: An optional contact ID for filtering the list. Only chats including this contact ID /// `query_contact_id`: An optional contact ID for filtering the list. Only chats including this contact ID
/// are returned. /// are returned.
pub fn try_load( pub async fn try_load(
context: &Context, context: &Context,
listflags: usize, listflags: usize,
query: Option<&str>, query: Option<&str>,
@@ -139,11 +139,13 @@ impl Chatlist {
params![MessageState::OutDraft, query_contact_id as i32, ChatVisibility::Pinned], params![MessageState::OutDraft, query_contact_id as i32, ChatVisibility::Pinned],
process_row, process_row,
process_rows, process_rows,
)? ).await?
} else if 0 != listflags & DC_GCL_ARCHIVED_ONLY { } else if 0 != listflags & DC_GCL_ARCHIVED_ONLY {
// show archived chats // show archived chats
context.sql.query_map( context
"SELECT c.id, m.id .sql
.query_map(
"SELECT c.id, m.id
FROM chats c FROM chats c
LEFT JOIN msgs m LEFT JOIN msgs m
ON c.id=m.chat_id ON c.id=m.chat_id
@@ -157,23 +159,26 @@ impl Chatlist {
AND c.archived=1 AND c.archived=1
GROUP BY c.id GROUP BY c.id
ORDER BY IFNULL(m.timestamp,c.created_timestamp) DESC, m.id DESC;", ORDER BY IFNULL(m.timestamp,c.created_timestamp) DESC, m.id DESC;",
params![MessageState::OutDraft], params![MessageState::OutDraft],
process_row, process_row,
process_rows, process_rows,
)? )
.await?
} else if let Some(query) = query { } else if let Some(query) = query {
let query = query.trim().to_string(); let query = query.trim().to_string();
ensure!(!query.is_empty(), "missing query"); ensure!(!query.is_empty(), "missing query");
// allow searching over special names that may change at any time // allow searching over special names that may change at any time
// when the ui calls set_stock_translation() // 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) warn!(context, "cannot update special chat names: {:?}", err)
} }
let str_like_cmd = format!("%{}%", query); let str_like_cmd = format!("%{}%", query);
context.sql.query_map( context
"SELECT c.id, m.id .sql
.query_map(
"SELECT c.id, m.id
FROM chats c FROM chats c
LEFT JOIN msgs m LEFT JOIN msgs m
ON c.id=m.chat_id ON c.id=m.chat_id
@@ -187,10 +192,11 @@ impl Chatlist {
AND c.name LIKE ? AND c.name LIKE ?
GROUP BY c.id GROUP BY c.id
ORDER BY IFNULL(m.timestamp,c.created_timestamp) DESC, m.id DESC;", ORDER BY IFNULL(m.timestamp,c.created_timestamp) DESC, m.id DESC;",
params![MessageState::OutDraft, str_like_cmd], params![MessageState::OutDraft, str_like_cmd],
process_row, process_row,
process_rows, process_rows,
)? )
.await?
} else { } else {
// show normal chatlist // show normal chatlist
let sort_id_up = if 0 != listflags & DC_GCL_FOR_FORWARDING { 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], params![MessageState::OutDraft, ChatVisibility::Archived, sort_id_up, ChatVisibility::Pinned],
process_row, process_row,
process_rows, process_rows,
)?; ).await?;
if 0 == listflags & DC_GCL_NO_SPECIALS { 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 { if 0 == listflags & DC_GCL_FOR_FORWARDING {
ids.insert( ids.insert(
0, 0,
@@ -233,7 +240,7 @@ impl Chatlist {
ids 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 { if ids.is_empty() && 0 != listflags & DC_GCL_ADD_ALLDONE_HINT {
ids.push((ChatId::new(DC_CHAT_ID_ALLDONE_HINT), MsgId::new(0))); 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::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()). /// - 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. // 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. // 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 // This is because we may want to display drafts here or stuff as
// "is typing". // "is typing".
@@ -300,7 +307,7 @@ impl Chatlist {
let chat_loaded: Chat; let chat_loaded: Chat;
let chat = if let Some(chat) = chat { let chat = if let Some(chat) = 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 = chat;
&chat_loaded &chat_loaded
} else { } else {
@@ -310,11 +317,11 @@ impl Chatlist {
let lastmsg_id = self.ids[index].1; let lastmsg_id = self.ids[index].1;
let mut lastcontact = None; 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 if lastmsg.from_id != DC_CONTACT_ID_SELF
&& (chat.typ == Chattype::Group || chat.typ == Chattype::VerifiedGroup) && (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) Some(lastmsg)
@@ -340,7 +347,7 @@ impl Chatlist {
} }
/// Returns the number of archived chats /// 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 context
.sql .sql
.query_get_value( .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;", "SELECT COUNT(*) FROM chats WHERE blocked=0 AND archived=1;",
params![], params![],
) )
.await
.unwrap_or_default() .unwrap_or_default()
} }
fn get_last_deaddrop_fresh_msg(context: &Context) -> Option<MsgId> { async fn get_last_deaddrop_fresh_msg(context: &Context) -> Option<MsgId> {
// We have an index over the state-column, this should be // We have an index over the state-column, this should be
// sufficient as there are typically only few fresh messages. // sufficient as there are typically only few fresh messages.
context.sql.query_get_value( context
context, .sql
concat!( .query_get_value(
"SELECT m.id", context,
" FROM msgs m", concat!(
" LEFT JOIN chats c", "SELECT m.id",
" ON c.id=m.chat_id", " FROM msgs m",
" WHERE m.state=10", " LEFT JOIN chats c",
" AND m.hidden=0", " ON c.id=m.chat_id",
" AND c.blocked=2", " WHERE m.state=10",
" ORDER BY m.timestamp DESC, m.id DESC;" " AND m.hidden=0",
), " AND c.blocked=2",
params![], " ORDER BY m.timestamp DESC, m.id DESC;"
) ),
params![],
)
.await
} }
#[cfg(test)] #[cfg(test)]
@@ -376,15 +387,21 @@ mod tests {
use crate::test_utils::*; use crate::test_utils::*;
#[test] #[async_std::test]
fn test_try_load() { async fn test_try_load() {
let t = dummy_context(); 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")
let chat_id2 = create_group_chat(&t.ctx, VerifiedStatus::Unverified, "b chat").unwrap(); .await
let chat_id3 = create_group_chat(&t.ctx, VerifiedStatus::Unverified, "c chat").unwrap(); .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 // 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.len(), 3);
assert_eq!(chats.get_chat_id(0), chat_id3); assert_eq!(chats.get_chat_id(0), chat_id3);
assert_eq!(chats.get_chat_id(1), chat_id2); assert_eq!(chats.get_chat_id(1), chat_id2);
@@ -394,20 +411,27 @@ mod tests {
let mut msg = Message::new(Viewtype::Text); let mut msg = Message::new(Viewtype::Text);
msg.set_text(Some("hello".to_string())); msg.set_text(Some("hello".to_string()));
chat_id2.set_draft(&t.ctx, Some(&mut msg)); chat_id2.set_draft(&t.ctx, Some(&mut msg));
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); assert_eq!(chats.get_chat_id(0), chat_id2);
// check chatlist query and archive functionality // 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); 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); assert_eq!(chats.len(), 0);
chat_id1 chat_id1
.set_visibility(&t.ctx, ChatVisibility::Archived) .set_visibility(&t.ctx, ChatVisibility::Archived)
.await
.ok(); .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); assert_eq!(chats.len(), 1);
} }
@@ -427,40 +451,50 @@ mod tests {
.is_self_talk()); .is_self_talk());
} }
#[test] #[async_std::test]
fn test_search_special_chat_names() { async fn test_search_special_chat_names() {
let t = dummy_context(); let t = dummy_context();
t.ctx.update_device_chats().unwrap(); 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); 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); assert_eq!(chats.len(), 0);
t.ctx t.ctx
.set_stock_translation(StockMessage::SavedMessages, "test-1234-save".to_string()) .set_stock_translation(StockMessage::SavedMessages, "test-1234-save".to_string())
.unwrap(); .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); assert_eq!(chats.len(), 1);
t.ctx t.ctx
.set_stock_translation(StockMessage::DeviceMessages, "test-5678-babbel".to_string()) .set_stock_translation(StockMessage::DeviceMessages, "test-5678-babbel".to_string())
.unwrap(); .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); assert_eq!(chats.len(), 1);
} }
#[test] #[async_std::test]
fn test_get_summary_unwrap() { async fn test_get_summary_unwrap() {
let t = dummy_context(); 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); let mut msg = Message::new(Viewtype::Text);
msg.set_text(Some("foo:\nbar \r\n test".to_string())); msg.set_text(Some("foo:\nbar \r\n test".to_string()));
chat_id1.set_draft(&t.ctx, Some(&mut msg)); chat_id1.set_draft(&t.ctx, Some(&mut msg));
let chats = Chatlist::try_load(&t.ctx, 0, None, None).unwrap(); let chats = Chatlist::try_load(&t.ctx, 0, None, None).await.unwrap();
let summary = chats.get_summary(&t.ctx, 0, None); 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 assert_eq!(summary.get_text2().unwrap(), "foo: bar test"); // the linebreak should be removed from summary
} }
} }

View File

@@ -95,16 +95,16 @@ pub enum Config {
impl Context { impl Context {
/// Get a configuration key. Returns `None` if no value is set, and no default value found. /// Get a configuration key. Returns `None` if no value is set, and no default value found.
pub fn get_config(&self, key: Config) -> Option<String> { pub async fn get_config(&self, key: Config) -> Option<String> {
let value = match key { let value = match key {
Config::Selfavatar => { 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()) rel_path.map(|p| dc_get_abs_path(self, &p).to_string_lossy().into_owned())
} }
Config::SysVersion => Some((&*DC_VERSION_STR).clone()), Config::SysVersion => Some((&*DC_VERSION_STR).clone()),
Config::SysMsgsizeMaxRecommended => Some(format!("{}", RECOMMENDED_FILE_SIZE)), Config::SysMsgsizeMaxRecommended => Some(format!("{}", RECOMMENDED_FILE_SIZE)),
Config::SysConfigKeys => Some(get_config_keys_string()), 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() { 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) self.get_config(key)
.await
.and_then(|s| s.parse().ok()) .and_then(|s| s.parse().ok())
.unwrap_or_default() .unwrap_or_default()
} }
pub fn get_config_bool(&self, key: Config) -> bool { pub async fn get_config_bool(&self, key: Config) -> bool {
self.get_config_int(key) != 0 self.get_config_int(key).await != 0
} }
/// Set the given config key. /// Set the given config key.
@@ -134,30 +135,34 @@ impl Context {
match key { match key {
Config::Selfavatar => { Config::Selfavatar => {
self.sql self.sql
.execute("UPDATE contacts SET selfavatar_sent=0;", NO_PARAMS)?; .execute("UPDATE contacts SET selfavatar_sent=0;", NO_PARAMS)
.await?;
self.sql self.sql
.set_raw_config_bool(self, "attach_selfavatar", true)?; .set_raw_config_bool(self, "attach_selfavatar", true)
.await?;
match value { match value {
Some(value) => { Some(value) => {
let blob = BlobObject::new_from_path(&self, value)?; let blob = BlobObject::new_from_path(&self, value)?;
blob.recode_to_avatar_size(self)?; 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 => { 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; interrupt_inbox_idle(self).await;
ret ret
} }
Config::SentboxWatch => { 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; interrupt_sentbox_idle(self).await;
ret ret
} }
Config::MvboxWatch => { 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; interrupt_mvbox_idle(self).await;
ret ret
} }
@@ -169,9 +174,9 @@ impl Context {
value 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(); .unwrap();
assert!(avatar_blob.exists()); assert!(avatar_blob.exists());
assert!(std::fs::metadata(&avatar_blob).unwrap().len() < avatar_bytes.len() as u64); 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())); assert_eq!(avatar_cfg, avatar_blob.to_str().map(|s| s.to_string()));
let img = image::open(avatar_src).unwrap(); let img = image::open(avatar_src).unwrap();
@@ -264,7 +269,7 @@ mod tests {
.set_config(Config::Selfavatar, Some(&avatar_src.to_str().unwrap())) .set_config(Config::Selfavatar, Some(&avatar_src.to_str().unwrap()))
.await .await
.unwrap(); .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())); assert_eq!(avatar_cfg, avatar_src.to_str().map(|s| s.to_string()));
let img = image::open(avatar_src).unwrap(); let img = image::open(avatar_src).unwrap();

View File

@@ -42,8 +42,8 @@ impl Context {
} }
/// Checks if the context is already configured. /// Checks if the context is already configured.
pub fn is_configured(&self) -> bool { pub async fn is_configured(&self) -> bool {
self.sql.get_raw_config_bool(self, "configured") self.sql.get_raw_config_bool(self, "configured").await
} }
} }
@@ -52,7 +52,7 @@ impl Context {
******************************************************************************/ ******************************************************************************/
#[allow(clippy::cognitive_complexity)] #[allow(clippy::cognitive_complexity)]
pub(crate) async fn job_configure_imap(context: &Context) -> job::Status { 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.",); error!(context, "Cannot configure, database not opened.",);
progress!(context, 0); progress!(context, 0);
return job::Status::Finished(Err(format_err!("Database not opened"))); return job::Status::Finished(Err(format_err!("Database not opened")));
@@ -75,7 +75,7 @@ pub(crate) async fn job_configure_imap(context: &Context) -> job::Status {
info!(context, "Configure ...",); info!(context, "Configure ...",);
// Variables that are shared between steps: // 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 // 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 // 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(); let mut param_domain = "undefined.undefined".to_owned();
@@ -115,6 +115,7 @@ pub(crate) async fn job_configure_imap(context: &Context) -> job::Status {
context context
.sql .sql
.set_raw_config(context, "addr", Some(param.addr.as_str())) .set_raw_config(context, "addr", Some(param.addr.as_str()))
.await
.ok(); .ok();
} }
progress!(context, 20); progress!(context, 20);
@@ -352,8 +353,8 @@ pub(crate) async fn job_configure_imap(context: &Context) -> job::Status {
} }
16 => { 16 => {
progress!(context, 900); progress!(context, 900);
let create_mvbox = context.get_config_bool(Config::MvboxWatch) let create_mvbox = context.get_config_bool(Config::MvboxWatch).await
|| context.get_config_bool(Config::MvboxMove); || context.get_config_bool(Config::MvboxMove).await;
let imap = &context.inbox_thread.imap; let imap = &context.inbox_thread.imap;
if let Err(err) = imap.ensure_configured_folders(context, create_mvbox).await { if let Err(err) = imap.ensure_configured_folders(context, create_mvbox).await {
warn!(context, "configuring folders failed: {:?}", err); warn!(context, "configuring folders failed: {:?}", err);
@@ -376,11 +377,13 @@ pub(crate) async fn job_configure_imap(context: &Context) -> job::Status {
context, context,
"configured_", /*the trailing underscore is correct*/ "configured_", /*the trailing underscore is correct*/
) )
.await
.ok(); .ok();
context context
.sql .sql
.set_raw_config_bool(context, "configured", true) .set_raw_config_bool(context, "configured", true)
.await
.ok(); .ok();
true 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, // 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 // 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. // (~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"); info!(context, "key generation completed");
progress!(context, 940); progress!(context, 940);
break; // We are done here 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. // this way, the parameters visible to the ui are always in-sync with the current configuration.
if success { if success {
LoginParam::from_database(context, "") LoginParam::from_database(context, "")
.await
.save_to_database(context, "configured_raw_") .save_to_database(context, "configured_raw_")
.await
.ok(); .ok();
} else { } else {
LoginParam::from_database(context, "configured_raw_") LoginParam::from_database(context, "configured_raw_")
.await
.save_to_database(context, "") .save_to_database(context, "")
.await
.ok(); .ok();
} }
@@ -428,7 +435,10 @@ pub(crate) async fn job_configure_imap(context: &Context) -> job::Status {
if !provider.after_login_hint.is_empty() { if !provider.after_login_hint.is_empty() {
let mut msg = Message::new(Viewtype::Text); let mut msg = Message::new(Viewtype::Text);
msg.text = Some(provider.after_login_hint.to_string()); 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"); warn!(context, "cannot add after_login_hint as core-provider-info");
} }
} }

View File

@@ -21,7 +21,6 @@ use crate::message::{MessageState, MsgId};
use crate::mimeparser::AvatarAction; use crate::mimeparser::AvatarAction;
use crate::param::*; use crate::param::*;
use crate::peerstate::*; use crate::peerstate::*;
use crate::sql;
use crate::stock::StockMessage; use crate::stock::StockMessage;
/// Contacts with at least this origin value are shown in the contact list. /// Contacts with at least this origin value are shown in the contact list.
@@ -164,29 +163,33 @@ pub enum VerifiedStatus {
} }
impl Contact { impl Contact {
pub fn load_from_db(context: &Context, contact_id: u32) -> crate::sql::Result<Self> { pub async fn load_from_db(context: &Context, contact_id: u32) -> crate::sql::Result<Self> {
let mut res = context.sql.query_row( let mut res = context
"SELECT c.name, c.addr, c.origin, c.blocked, c.authname, c.param .sql
.query_row(
"SELECT c.name, c.addr, c.origin, c.blocked, c.authname, c.param
FROM contacts c FROM contacts c
WHERE c.id=?;", WHERE c.id=?;",
params![contact_id as i32], params![contact_id as i32],
|row| { |row| {
let contact = Self { let contact = Self {
id: contact_id, id: contact_id,
name: row.get::<_, String>(0)?, name: row.get::<_, String>(0)?,
authname: row.get::<_, String>(4)?, authname: row.get::<_, String>(4)?,
addr: row.get::<_, String>(1)?, addr: row.get::<_, String>(1)?,
blocked: row.get::<_, Option<i32>>(3)?.unwrap_or_default() != 0, blocked: row.get::<_, Option<i32>>(3)?.unwrap_or_default() != 0,
origin: row.get(2)?, origin: row.get(2)?,
param: row.get::<_, String>(5)?.parse().unwrap_or_default(), param: row.get::<_, String>(5)?.parse().unwrap_or_default(),
}; };
Ok(contact) Ok(contact)
}, },
)?; )
.await?;
if contact_id == DC_CONTACT_ID_SELF { if contact_id == DC_CONTACT_ID_SELF {
res.name = context.stock_str(StockMessage::SelfMsg).to_string(); res.name = context.stock_str(StockMessage::SelfMsg).to_string();
res.addr = context res.addr = context
.get_config(Config::ConfiguredAddr) .get_config(Config::ConfiguredAddr)
.await
.unwrap_or_default(); .unwrap_or_default();
} else if contact_id == DC_CONTACT_ID_DEVICE { } else if contact_id == DC_CONTACT_ID_DEVICE {
res.name = context.stock_str(StockMessage::DeviceMessages).to_string(); res.name = context.stock_str(StockMessage::DeviceMessages).to_string();
@@ -201,20 +204,21 @@ impl Contact {
} }
/// Check if a contact is blocked. /// 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) Self::load_from_db(context, id)
.await
.map(|contact| contact.blocked) .map(|contact| contact.blocked)
.unwrap_or_default() .unwrap_or_default()
} }
/// Block the given contact. /// Block the given contact.
pub fn block(context: &Context, id: u32) { pub async fn block(context: &Context, id: u32) {
set_block_contact(context, id, true); set_block_contact(context, id, true).await;
} }
/// Unblock the given contact. /// Unblock the given contact.
pub fn unblock(context: &Context, id: u32) { pub async fn unblock(context: &Context, id: u32) {
set_block_contact(context, id, false); set_block_contact(context, id, false).await;
} }
/// Add a single contact as a result of an _explicit_ user action. /// Add a single contact as a result of an _explicit_ user action.
@@ -226,15 +230,19 @@ impl Contact {
/// a bunch of addresses. /// a bunch of addresses.
/// ///
/// May result in a `#DC_EVENT_CONTACTS_CHANGED` event. /// May result in a `#DC_EVENT_CONTACTS_CHANGED` event.
pub fn create(context: &Context, name: impl AsRef<str>, addr: impl AsRef<str>) -> Result<u32> { pub async fn create(
context: &Context,
name: impl AsRef<str>,
addr: impl AsRef<str>,
) -> Result<u32> {
ensure!( ensure!(
!addr.as_ref().is_empty(), !addr.as_ref().is_empty(),
"Cannot create contact with empty address" "Cannot create contact with empty address"
); );
let (contact_id, sth_modified) = let (contact_id, sth_modified) =
Contact::add_or_lookup(context, name, addr, Origin::ManuallyCreated)?; Contact::add_or_lookup(context, name, addr, Origin::ManuallyCreated).await?;
let blocked = Contact::is_blocked_load(context, contact_id); let blocked = Contact::is_blocked_load(context, contact_id).await;
context.call_cb(Event::ContactsChanged( context.call_cb(Event::ContactsChanged(
if sth_modified == Modifier::Created { if sth_modified == Modifier::Created {
Some(contact_id) Some(contact_id)
@@ -253,14 +261,15 @@ impl Contact {
/// as *noticed*. See also dc_marknoticed_chat() and dc_markseen_msgs() /// as *noticed*. See also dc_marknoticed_chat() and dc_markseen_msgs()
/// ///
/// Calling this function usually results in the event `#DC_EVENT_MSGS_CHANGED`. /// Calling this function usually results in the event `#DC_EVENT_MSGS_CHANGED`.
pub fn mark_noticed(context: &Context, id: u32) { pub async fn mark_noticed(context: &Context, id: u32) {
if sql::execute( if context
context, .sql
&context.sql, .execute(
"UPDATE msgs SET state=? WHERE from_id=? AND state=?;", "UPDATE msgs SET state=? WHERE from_id=? AND state=?;",
params![MessageState::InNoticed, id as i32, MessageState::InFresh], params![MessageState::InNoticed, id as i32, MessageState::InFresh],
) )
.is_ok() .await
.is_ok()
{ {
context.call_cb(Event::MsgsChanged { context.call_cb(Event::MsgsChanged {
chat_id: ChatId::new(0), chat_id: ChatId::new(0),
@@ -274,7 +283,7 @@ impl Contact {
/// ///
/// To validate an e-mail address independently of the contact database /// To validate an e-mail address independently of the contact database
/// use `dc_may_be_valid_addr()`. /// use `dc_may_be_valid_addr()`.
pub fn lookup_id_by_addr(context: &Context, addr: impl AsRef<str>) -> u32 { pub async fn lookup_id_by_addr(context: &Context, addr: impl AsRef<str>) -> u32 {
if addr.as_ref().is_empty() { if addr.as_ref().is_empty() {
return 0; return 0;
} }
@@ -282,6 +291,7 @@ impl Contact {
let addr_normalized = addr_normalize(addr.as_ref()); let addr_normalized = addr_normalize(addr.as_ref());
let addr_self = context let addr_self = context
.get_config(Config::ConfiguredAddr) .get_config(Config::ConfiguredAddr)
.await
.unwrap_or_default(); .unwrap_or_default();
if addr_cmp(addr_normalized, addr_self) { if addr_cmp(addr_normalized, addr_self) {
@@ -296,7 +306,7 @@ impl Contact {
DC_CONTACT_ID_LAST_SPECIAL as i32, DC_CONTACT_ID_LAST_SPECIAL as i32,
DC_ORIGIN_MIN_CONTACT_LIST, DC_ORIGIN_MIN_CONTACT_LIST,
], ],
).unwrap_or_default() ).await.unwrap_or_default()
} }
/// Lookup a contact and create it if it does not exist yet. /// 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". /// 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. /// 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, context: &Context,
name: impl AsRef<str>, name: impl AsRef<str>,
addr: impl AsRef<str>, addr: impl AsRef<str>,
@@ -341,6 +351,7 @@ impl Contact {
let addr = addr_normalize(addr.as_ref()); let addr = addr_normalize(addr.as_ref());
let addr_self = context let addr_self = context
.get_config(Config::ConfiguredAddr) .get_config(Config::ConfiguredAddr)
.await
.unwrap_or_default(); .unwrap_or_default();
if addr_cmp(addr, addr_self) { if addr_cmp(addr, addr_self) {
@@ -396,7 +407,8 @@ impl Contact {
Ok((row_id, row_name, row_addr, row_origin, row_authname)) Ok((row_id, row_name, row_addr, row_origin, row_authname))
}, },
) { )
.await {
row_id = id; row_id = id;
if origin as i32 >= row_origin as i32 && addr != row_addr { if origin as i32 >= row_origin as i32 && addr != row_addr {
update_addr = true; update_addr = true;
@@ -412,37 +424,36 @@ impl Contact {
&row_name &row_name
}; };
sql::execute( context
context, .sql
&context.sql, .execute(
"UPDATE contacts SET name=?, addr=?, origin=?, authname=? WHERE id=?;", "UPDATE contacts SET name=?, addr=?, origin=?, authname=? WHERE id=?;",
params![ params![
new_name, new_name,
if update_addr { addr } else { &row_addr }, if update_addr { addr } else { &row_addr },
if origin > row_origin { if origin > row_origin {
origin origin
} else { } else {
row_origin row_origin
}, },
if update_authname { if update_authname {
name.as_ref() name.as_ref()
} else { } else {
&row_authname &row_authname
}, },
row_id row_id
], ],
) )
.ok(); .await
.ok();
if update_name { if update_name {
// Update the contact name also if it is used as a group 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. // This is one of the few duplicated data, however, getting the chat list is easier this way.
sql::execute( context.sql.execute(
context,
&context.sql,
"UPDATE chats SET name=? WHERE type=? AND id IN(SELECT chat_id FROM chats_contacts WHERE contact_id=?);", "UPDATE chats SET name=? WHERE type=? AND id IN(SELECT chat_id FROM chats_contacts WHERE contact_id=?);",
params![new_name, Chattype::Single, row_id] params![new_name, Chattype::Single, row_id]
).ok(); ).await.ok();
} }
sth_modified = Modifier::Modified; sth_modified = Modifier::Modified;
} }
@@ -451,20 +462,24 @@ impl Contact {
update_authname = true; update_authname = true;
} }
if sql::execute( if context
context, .sql
&context.sql, .execute(
"INSERT INTO contacts (name, addr, origin, authname) VALUES(?, ?, ?, ?);", "INSERT INTO contacts (name, addr, origin, authname) VALUES(?, ?, ?, ?);",
params![ params![
name.as_ref(), name.as_ref(),
addr, addr,
origin, origin,
if update_authname { name.as_ref() } else { "" } if update_authname { name.as_ref() } else { "" }
], ],
) )
.is_ok() .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; sth_modified = Modifier::Created;
info!(context, "added contact id={} addr={}", row_id, addr); info!(context, "added contact id={} addr={}", row_id, addr);
} else { } 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`. /// The `addr_book` is a multiline string in the format `Name one\nAddress one\nName two\nAddress two`.
/// ///
/// Returns the number of modified contacts. /// Returns the number of modified contacts.
pub fn add_address_book(context: &Context, addr_book: impl AsRef<str>) -> Result<usize> { pub async fn add_address_book(context: &Context, addr_book: impl AsRef<str>) -> Result<usize> {
let mut modify_cnt = 0; let mut modify_cnt = 0;
for (name, addr) in split_address_book(addr_book.as_ref()).into_iter() { for (name, addr) in split_address_book(addr_book.as_ref()).into_iter() {
let name = normalize_name(name); 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) => { Err(err) => {
warn!( warn!(
context, context,
@@ -527,13 +542,14 @@ impl Contact {
/// - if the flag DC_GCL_VERIFIED_ONLY is set, only verified contacts are returned. /// - 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. /// if DC_GCL_VERIFIED_ONLY is not set, verified and unverified contacts are returned.
/// `query` is a string to filter the list. /// `query` is a string to filter the list.
pub fn get_all( pub async fn get_all(
context: &Context, context: &Context,
listflags: u32, listflags: u32,
query: Option<impl AsRef<str>>, query: Option<impl AsRef<str>>,
) -> Result<Vec<u32>> { ) -> Result<Vec<u32>> {
let self_addr = context let self_addr = context
.get_config(Config::ConfiguredAddr) .get_config(Config::ConfiguredAddr)
.await
.unwrap_or_default(); .unwrap_or_default();
let mut add_self = false; let mut add_self = false;
@@ -549,8 +565,10 @@ impl Contact {
.map(|s| s.as_ref().to_string()) .map(|s| s.as_ref().to_string())
.unwrap_or_default() .unwrap_or_default()
); );
context.sql.query_map( context
"SELECT c.id FROM contacts c \ .sql
.query_map(
"SELECT c.id FROM contacts c \
LEFT JOIN acpeerstates ps ON c.addr=ps.addr \ LEFT JOIN acpeerstates ps ON c.addr=ps.addr \
WHERE c.addr!=?1 \ WHERE c.addr!=?1 \
AND c.id>?2 \ AND c.id>?2 \
@@ -559,24 +577,28 @@ impl Contact {
AND (c.name LIKE ?4 OR c.addr LIKE ?5) \ AND (c.name LIKE ?4 OR c.addr LIKE ?5) \
AND (1=?6 OR LENGTH(ps.verified_key_fingerprint)!=0) \ AND (1=?6 OR LENGTH(ps.verified_key_fingerprint)!=0) \
ORDER BY LOWER(c.name||c.addr),c.id;", ORDER BY LOWER(c.name||c.addr),c.id;",
params![ params![
self_addr, self_addr,
DC_CONTACT_ID_LAST_SPECIAL as i32, DC_CONTACT_ID_LAST_SPECIAL as i32,
Origin::IncomingReplyTo, Origin::IncomingReplyTo,
&s3str_like_cmd, &s3str_like_cmd,
&s3str_like_cmd, &s3str_like_cmd,
if flag_verified_only { 0 } else { 1 }, if flag_verified_only { 0 } else { 1 },
], ],
|row| row.get::<_, i32>(0), |row| row.get::<_, i32>(0),
|ids| { |ids| {
for id in ids { for id in ids {
ret.push(id? as u32); ret.push(id? as u32);
} }
Ok(()) 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); let self_name2 = context.stock_str(StockMessage::SelfMsg);
if let Some(query) = query { if let Some(query) = query {
@@ -602,7 +624,7 @@ impl Contact {
} }
Ok(()) Ok(())
} }
)?; ).await?;
} }
if flag_add_self && add_self { if flag_add_self && add_self {
@@ -612,7 +634,7 @@ impl Contact {
Ok(ret) Ok(ret)
} }
pub fn get_blocked_cnt(context: &Context) -> usize { pub async fn get_blocked_cnt(context: &Context) -> usize {
context context
.sql .sql
.query_get_value::<_, isize>( .query_get_value::<_, isize>(
@@ -620,11 +642,12 @@ impl Contact {
"SELECT COUNT(*) FROM contacts WHERE id>? AND blocked!=0", "SELECT COUNT(*) FROM contacts WHERE id>? AND blocked!=0",
params![DC_CONTACT_ID_LAST_SPECIAL as i32], params![DC_CONTACT_ID_LAST_SPECIAL as i32],
) )
.await
.unwrap_or_default() as usize .unwrap_or_default() as usize
} }
/// Get blocked contacts. /// Get blocked contacts.
pub fn get_all_blocked(context: &Context) -> Vec<u32> { pub async fn get_all_blocked(context: &Context) -> Vec<u32> {
context context
.sql .sql
.query_map( .query_map(
@@ -636,6 +659,7 @@ impl Contact {
.map_err(Into::into) .map_err(Into::into)
}, },
) )
.await
.unwrap_or_default() .unwrap_or_default()
} }
@@ -644,14 +668,14 @@ impl Contact {
/// This function returns a string explaining the encryption state /// This function returns a string explaining the encryption state
/// of the contact and if the connection is encrypted the /// of the contact and if the connection is encrypted the
/// fingerprints of the keys involved. /// fingerprints of the keys involved.
pub fn get_encrinfo(context: &Context, contact_id: u32) -> Result<String> { pub async fn get_encrinfo(context: &Context, contact_id: u32) -> Result<String> {
let mut ret = String::new(); 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 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() if peerstate.is_some()
&& peerstate && peerstate
@@ -668,8 +692,8 @@ impl Contact {
}); });
ret += &p; ret += &p;
if self_key.is_none() { if self_key.is_none() {
e2ee::ensure_secret_key_exists(context)?; e2ee::ensure_secret_key_exists(context).await?;
self_key = Key::from_self_public(context, &loginparam.addr, &context.sql); self_key = Key::from_self_public(context, &loginparam.addr, &context.sql).await;
} }
let p = context.stock_str(StockMessage::FingerPrints); let p = context.stock_str(StockMessage::FingerPrints);
ret += &format!(" {}:", p); ret += &format!(" {}:", p);
@@ -718,7 +742,7 @@ impl Contact {
/// possible as the contact is in use. In this case, the contact can be blocked. /// possible as the contact is in use. In this case, the contact can be blocked.
/// ///
/// May result in a `#DC_EVENT_CONTACTS_CHANGED` event. /// 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!( ensure!(
contact_id > DC_CONTACT_ID_LAST_SPECIAL, contact_id > DC_CONTACT_ID_LAST_SPECIAL,
"Can not delete special contact" "Can not delete special contact"
@@ -731,6 +755,7 @@ impl Contact {
"SELECT COUNT(*) FROM chats_contacts WHERE contact_id=?;", "SELECT COUNT(*) FROM chats_contacts WHERE contact_id=?;",
params![contact_id as i32], params![contact_id as i32],
) )
.await
.unwrap_or_default(); .unwrap_or_default();
let count_msgs: i32 = if count_contacts > 0 { let count_msgs: i32 = if count_contacts > 0 {
@@ -741,18 +766,21 @@ impl Contact {
"SELECT COUNT(*) FROM msgs WHERE from_id=? OR to_id=?;", "SELECT COUNT(*) FROM msgs WHERE from_id=? OR to_id=?;",
params![contact_id as i32, contact_id as i32], params![contact_id as i32, contact_id as i32],
) )
.await
.unwrap_or_default() .unwrap_or_default()
} else { } else {
0 0
}; };
if count_msgs == 0 { if count_msgs == 0 {
match sql::execute( match context
context, .sql
&context.sql, .execute(
"DELETE FROM contacts WHERE id=?;", "DELETE FROM contacts WHERE id=?;",
params![contact_id as i32], params![contact_id as i32],
) { )
.await
{
Ok(_) => { Ok(_) => {
context.call_cb(Event::ContactsChanged(None)); context.call_cb(Event::ContactsChanged(None));
return Ok(()); return Ok(());
@@ -776,17 +804,20 @@ impl Contact {
/// For contact DC_CONTACT_ID_SELF (1), the function returns sth. /// For contact DC_CONTACT_ID_SELF (1), the function returns sth.
/// like "Me" in the selected language and the email address /// like "Me" in the selected language and the email address
/// defined by dc_set_config(). /// defined by dc_set_config().
pub fn get_by_id(context: &Context, contact_id: u32) -> Result<Contact> { pub async fn get_by_id(context: &Context, contact_id: u32) -> Result<Contact> {
Ok(Contact::load_from_db(context, contact_id)?) let contact = Contact::load_from_db(context, contact_id).await?;
Ok(contact)
} }
pub fn update_param(&mut self, context: &Context) -> Result<()> { pub async fn update_param(&mut self, context: &Context) -> Result<()> {
sql::execute( context
context, .sql
&context.sql, .execute(
"UPDATE contacts SET param=? WHERE id=?", "UPDATE contacts SET param=? WHERE id=?",
params![self.param.to_string(), self.id as i32], params![self.param.to_string(), self.id as i32],
)?; )
.await?;
Ok(()) Ok(())
} }
@@ -856,9 +887,9 @@ impl Contact {
/// Get the contact's profile image. /// Get the contact's profile image.
/// This is the image set by each remote user on their own /// This is the image set by each remote user on their own
/// using dc_set_config(context, "selfavatar", image). /// using dc_set_config(context, "selfavatar", image).
pub fn get_profile_image(&self, context: &Context) -> Option<PathBuf> { pub async fn get_profile_image(&self, context: &Context) -> Option<PathBuf> {
if self.id == DC_CONTACT_ID_SELF { 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)); return Some(PathBuf::from(p));
} }
} else if let Some(image_rel) = self.param.get(Param::ProfileImage) { } else if let Some(image_rel) = self.param.get(Param::ProfileImage) {
@@ -916,12 +947,16 @@ impl Contact {
VerifiedStatus::Unverified VerifiedStatus::Unverified
} }
pub fn addr_equals_contact(context: &Context, addr: impl AsRef<str>, contact_id: u32) -> bool { pub async fn addr_equals_contact(
context: &Context,
addr: impl AsRef<str>,
contact_id: u32,
) -> bool {
if addr.as_ref().is_empty() { if addr.as_ref().is_empty() {
return false; 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() { if !contact.addr.is_empty() {
let normalized_addr = addr_normalize(addr.as_ref()); let normalized_addr = addr_normalize(addr.as_ref());
if contact.addr == normalized_addr { if contact.addr == normalized_addr {
@@ -933,8 +968,8 @@ impl Contact {
false false
} }
pub fn get_real_cnt(context: &Context) -> usize { pub async fn get_real_cnt(context: &Context) -> usize {
if !context.sql.is_open() { if !context.sql.is_open().await {
return 0; return 0;
} }
@@ -945,11 +980,12 @@ impl Contact {
"SELECT COUNT(*) FROM contacts WHERE id>?;", "SELECT COUNT(*) FROM contacts WHERE id>?;",
params![DC_CONTACT_ID_LAST_SPECIAL as i32], params![DC_CONTACT_ID_LAST_SPECIAL as i32],
) )
.await
.unwrap_or_default() as usize .unwrap_or_default() as usize
} }
pub fn real_exists_by_id(context: &Context, contact_id: u32) -> bool { pub async fn real_exists_by_id(context: &Context, contact_id: u32) -> bool {
if !context.sql.is_open() || contact_id <= DC_CONTACT_ID_LAST_SPECIAL { if !context.sql.is_open().await || contact_id <= DC_CONTACT_ID_LAST_SPECIAL {
return false; return false;
} }
@@ -959,16 +995,18 @@ impl Contact {
"SELECT id FROM contacts WHERE id=?;", "SELECT id FROM contacts WHERE id=?;",
params![contact_id as i32], params![contact_id as i32],
) )
.await
.unwrap_or_default() .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 context
.sql .sql
.execute( .execute(
"UPDATE contacts SET origin=? WHERE id=? AND origin<?;", "UPDATE contacts SET origin=? WHERE id=? AND origin<?;",
params![origin, contact_id as i32, origin], params![origin, contact_id as i32, origin],
) )
.await
.is_ok() .is_ok()
} }
} }
@@ -995,32 +1033,31 @@ pub fn addr_normalize(addr: &str) -> &str {
norm 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 { if contact_id <= DC_CONTACT_ID_LAST_SPECIAL {
return; 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 if contact.blocked != new_blocking
&& sql::execute( && context
context, .sql
&context.sql, .execute(
"UPDATE contacts SET blocked=? WHERE id=?;", "UPDATE contacts SET blocked=? WHERE id=?;",
params![new_blocking as i32, contact_id as i32], params![new_blocking as i32, contact_id as i32],
) )
.is_ok() .await
.is_ok()
{ {
// also (un)block all chats with _only_ this contact - we do not delete them to allow a // also (un)block all chats with _only_ this contact - we do not delete them to allow a
// non-destructive blocking->unblocking. // non-destructive blocking->unblocking.
// (Maybe, beside normal chats (type=100) we should also block group chats with only this user. // (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; // 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...) // this would result in recreating the same group...)
if sql::execute( if context.sql.execute(
context,
&context.sql,
"UPDATE chats SET blocked=? WHERE type=? AND id IN (SELECT chat_id FROM chats_contacts WHERE contact_id=?);", "UPDATE chats SET blocked=? WHERE type=? AND id IN (SELECT chat_id FROM chats_contacts WHERE contact_id=?);",
params![new_blocking, 100, contact_id as i32], params![new_blocking, 100, contact_id as i32],
).is_ok() { ).await.is_ok() {
Contact::mark_noticed(context, contact_id); Contact::mark_noticed(context, contact_id);
context.call_cb(Event::ContactsChanged(None)); 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, context: &Context,
contact_id: u32, contact_id: u32,
profile_image: &AvatarAction, profile_image: &AvatarAction,
) -> Result<()> { ) -> Result<()> {
// the given profile image is expected to be already in the blob directory // 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. // 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 { let changed = match profile_image {
AvatarAction::Change(profile_image) => { AvatarAction::Change(profile_image) => {
contact.param.set(Param::ProfileImage, profile_image); contact.param.set(Param::ProfileImage, profile_image);
@@ -1047,7 +1084,7 @@ pub(crate) fn set_profile_image(
} }
}; };
if changed { if changed {
contact.update_param(context)?; contact.update_param(context).await?;
context.call_cb(Event::ContactsChanged(Some(contact_id))); context.call_cb(Event::ContactsChanged(Some(contact_id)));
} }
Ok(()) Ok(())
@@ -1119,8 +1156,8 @@ fn cat_fingerprint(
impl Context { impl Context {
/// determine whether the specified addr maps to the/a self addr /// determine whether the specified addr maps to the/a self addr
pub fn is_self_addr(&self, addr: &str) -> Result<bool> { pub async fn is_self_addr(&self, addr: &str) -> Result<bool> {
let self_addr = match self.get_config(Config::ConfiguredAddr) { let self_addr = match self.get_config(Config::ConfiguredAddr).await {
Some(s) => s, Some(s) => s,
None => return Err(Error::NotConfigured), None => return Err(Error::NotConfigured),
}; };
@@ -1222,11 +1259,11 @@ mod tests {
#[async_std::test] #[async_std::test]
async fn test_is_self_addr() -> Result<()> { async fn test_is_self_addr() -> Result<()> {
let t = test_context(None); let t = test_context(None);
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; 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("me@me.org").await?, false);
assert_eq!(t.ctx.is_self_addr(&addr)?, true); assert_eq!(t.ctx.is_self_addr(&addr).await?, true);
Ok(()) Ok(())
} }

View File

@@ -3,7 +3,7 @@
use std::collections::HashMap; use std::collections::HashMap;
use std::ffi::OsString; use std::ffi::OsString;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::sync::{Arc, Mutex, RwLock}; use std::sync::{atomic::AtomicBool, Arc, Mutex, RwLock};
use crate::chat::*; use crate::chat::*;
use crate::config::Config; use crate::config::Config;
@@ -39,8 +39,8 @@ pub struct Context {
/// Blob directory path /// Blob directory path
blobdir: PathBuf, blobdir: PathBuf,
pub sql: Sql, pub sql: Sql,
pub perform_inbox_jobs_needed: Arc<RwLock<bool>>, pub perform_inbox_jobs_needed: AtomicBool,
pub probe_imap_network: Arc<RwLock<bool>>, pub probe_imap_network: AtomicBool,
pub inbox_thread: JobThread, pub inbox_thread: JobThread,
pub sentbox_thread: JobThread, pub sentbox_thread: JobThread,
pub mvbox_thread: JobThread, pub mvbox_thread: JobThread,
@@ -81,7 +81,11 @@ pub fn get_info() -> HashMap<&'static str, String> {
impl Context { impl Context {
/// Creates new context. /// Creates new context.
pub fn new(cb: Box<ContextCallback>, os_name: String, dbfile: PathBuf) -> Result<Context> { pub async fn new(
cb: Box<ContextCallback>,
os_name: String,
dbfile: PathBuf,
) -> Result<Context> {
pretty_env_logger::try_init_timed().ok(); pretty_env_logger::try_init_timed().ok();
let mut blob_fname = OsString::new(); let mut blob_fname = OsString::new();
@@ -91,10 +95,10 @@ impl Context {
if !blobdir.exists() { if !blobdir.exists() {
std::fs::create_dir_all(&blobdir)?; 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<ContextCallback>, cb: Box<ContextCallback>,
os_name: String, os_name: String,
dbfile: PathBuf, dbfile: PathBuf,
@@ -120,14 +124,14 @@ impl Context {
inbox_thread: JobThread::new("INBOX", "configured_inbox_folder", Imap::new()), inbox_thread: JobThread::new("INBOX", "configured_inbox_folder", Imap::new()),
sentbox_thread: JobThread::new("SENTBOX", "configured_sentbox_folder", Imap::new()), sentbox_thread: JobThread::new("SENTBOX", "configured_sentbox_folder", Imap::new()),
mvbox_thread: JobThread::new("MVBOX", "configured_mvbox_folder", Imap::new()), mvbox_thread: JobThread::new("MVBOX", "configured_mvbox_folder", Imap::new()),
probe_imap_network: Arc::new(RwLock::new(false)), probe_imap_network: Default::default(),
perform_inbox_jobs_needed: Arc::new(RwLock::new(false)), perform_inbox_jobs_needed: Default::default(),
generating_key_mutex: Mutex::new(()), generating_key_mutex: Mutex::new(()),
translated_stockstrings: RwLock::new(HashMap::new()), translated_stockstrings: RwLock::new(HashMap::new()),
}; };
ensure!( ensure!(
ctx.sql.open(&ctx, &ctx.dbfile, false), ctx.sql.open(&ctx, &ctx.dbfile, false).await,
"Failed opening sqlite database" "Failed opening sqlite database"
); );
@@ -208,57 +212,66 @@ impl Context {
* UI chat/message related API * 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 unset = "0";
let l = LoginParam::from_database(self, ""); let l = LoginParam::from_database(self, "").await;
let l2 = LoginParam::from_database(self, "configured_"); let l2 = LoginParam::from_database(self, "configured_").await;
let displayname = self.get_config(Config::Displayname); let displayname = self.get_config(Config::Displayname).await;
let chats = get_chat_cnt(self) as usize; let chats = get_chat_cnt(self).await as usize;
let real_msgs = message::get_real_msg_cnt(self) as usize; let real_msgs = message::get_real_msg_cnt(self) as usize;
let deaddrop_msgs = message::get_deaddrop_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 contacts = Contact::get_real_cnt(self).await as usize;
let is_configured = self.get_config_int(Config::Configured); let is_configured = self.get_config_int(Config::Configured).await;
let dbversion = self let dbversion = self
.sql .sql
.get_raw_config_int(self, "dbversion") .get_raw_config_int(self, "dbversion")
.await
.unwrap_or_default(); .unwrap_or_default();
let e2ee_enabled = self.get_config_int(Config::E2eeEnabled); let e2ee_enabled = self.get_config_int(Config::E2eeEnabled).await;
let mdns_enabled = self.get_config_int(Config::MdnsEnabled); let mdns_enabled = self.get_config_int(Config::MdnsEnabled).await;
let bcc_self = self.get_config_int(Config::BccSelf); let bcc_self = self.get_config_int(Config::BccSelf).await;
let prv_key_cnt: Option<isize> = let prv_key_cnt: Option<isize> = self
self.sql .sql
.query_get_value(self, "SELECT COUNT(*) FROM keypairs;", rusqlite::NO_PARAMS); .query_get_value(self, "SELECT COUNT(*) FROM keypairs;", rusqlite::NO_PARAMS)
.await;
let pub_key_cnt: Option<isize> = self.sql.query_get_value( let pub_key_cnt: Option<isize> = self
self, .sql
"SELECT COUNT(*) FROM acpeerstates;", .query_get_value(
rusqlite::NO_PARAMS, 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) { let fingerprint_str =
key.fingerprint() if let Some(key) = Key::from_self_public(self, &l2.addr, &self.sql).await {
} else { key.fingerprint()
"<Not yet calculated>".into() } else {
}; "<Not yet calculated>".into()
};
let inbox_watch = self.get_config_int(Config::InboxWatch); let inbox_watch = self.get_config_int(Config::InboxWatch).await;
let sentbox_watch = self.get_config_int(Config::SentboxWatch); let sentbox_watch = self.get_config_int(Config::SentboxWatch).await;
let mvbox_watch = self.get_config_int(Config::MvboxWatch); let mvbox_watch = self.get_config_int(Config::MvboxWatch).await;
let mvbox_move = self.get_config_int(Config::MvboxMove); let mvbox_move = self.get_config_int(Config::MvboxMove).await;
let folders_configured = self let folders_configured = self
.sql .sql
.get_raw_config_int(self, "folders_configured") .get_raw_config_int(self, "folders_configured")
.await
.unwrap_or_default(); .unwrap_or_default();
let configured_sentbox_folder = self let configured_sentbox_folder = self
.sql .sql
.get_raw_config(self, "configured_sentbox_folder") .get_raw_config(self, "configured_sentbox_folder")
.await
.unwrap_or_else(|| "<unset>".to_string()); .unwrap_or_else(|| "<unset>".to_string());
let configured_mvbox_folder = self let configured_mvbox_folder = self
.sql .sql
.get_raw_config(self, "configured_mvbox_folder") .get_raw_config(self, "configured_mvbox_folder")
.await
.unwrap_or_else(|| "<unset>".to_string()); .unwrap_or_else(|| "<unset>".to_string());
let mut res = get_info(); let mut res = get_info();
@@ -273,6 +286,7 @@ impl Context {
res.insert( res.insert(
"selfavatar", "selfavatar",
self.get_config(Config::Selfavatar) self.get_config(Config::Selfavatar)
.await
.unwrap_or_else(|| "<unset>".to_string()), .unwrap_or_else(|| "<unset>".to_string()),
); );
res.insert("is_configured", is_configured.to_string()); res.insert("is_configured", is_configured.to_string());
@@ -301,7 +315,7 @@ impl Context {
res res
} }
pub fn get_fresh_msgs(&self) -> Vec<MsgId> { pub async fn get_fresh_msgs(&self) -> Vec<MsgId> {
let show_deaddrop = 0; let show_deaddrop = 0;
self.sql self.sql
.query_map( .query_map(
@@ -329,11 +343,12 @@ impl Context {
Ok(ret) Ok(ret)
}, },
) )
.await
.unwrap_or_default() .unwrap_or_default()
} }
#[allow(non_snake_case)] #[allow(non_snake_case)]
pub fn search_msgs(&self, chat_id: ChatId, query: impl AsRef<str>) -> Vec<MsgId> { pub async fn search_msgs(&self, chat_id: ChatId, query: impl AsRef<str>) -> Vec<MsgId> {
let real_query = query.as_ref().trim(); let real_query = query.as_ref().trim();
if real_query.is_empty() { if real_query.is_empty() {
return Vec::new(); return Vec::new();
@@ -383,6 +398,7 @@ impl Context {
Ok(ret) Ok(ret)
}, },
) )
.await
.unwrap_or_default() .unwrap_or_default()
} }
@@ -390,8 +406,11 @@ impl Context {
folder_name.as_ref() == "INBOX" folder_name.as_ref() == "INBOX"
} }
pub fn is_sentbox(&self, folder_name: impl AsRef<str>) -> bool { pub async fn is_sentbox(&self, folder_name: impl AsRef<str>) -> bool {
let sentbox_name = self.sql.get_raw_config(self, "configured_sentbox_folder"); let sentbox_name = self
.sql
.get_raw_config(self, "configured_sentbox_folder")
.await;
if let Some(name) = sentbox_name { if let Some(name) = sentbox_name {
name == folder_name.as_ref() name == folder_name.as_ref()
} else { } else {
@@ -399,8 +418,11 @@ impl Context {
} }
} }
pub fn is_mvbox(&self, folder_name: impl AsRef<str>) -> bool { pub async fn is_mvbox(&self, folder_name: impl AsRef<str>) -> bool {
let mvbox_name = self.sql.get_raw_config(self, "configured_mvbox_folder"); let mvbox_name = self
.sql
.get_raw_config(self, "configured_mvbox_folder")
.await;
if let Some(name) = mvbox_name { if let Some(name) = mvbox_name {
name == folder_name.as_ref() name == folder_name.as_ref()
@@ -410,14 +432,14 @@ impl Context {
} }
pub async fn do_heuristics_moves(&self, folder: &str, msg_id: MsgId) { 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; return;
} }
if self.is_mvbox(folder) { if self.is_mvbox(folder).await {
return; 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() { if msg.is_setupmessage() {
// do not move setup messages; // do not move setup messages;
// there may be a non-delta device that wants to handle it // there may be a non-delta device that wants to handle it
@@ -567,11 +589,11 @@ mod tests {
std::mem::drop(t.ctx); std::mem::drop(t.ctx);
} }
#[test] #[async_std::test]
fn test_get_info() { async fn test_get_info() {
let t = dummy_context(); let t = dummy_context();
let info = t.ctx.get_info(); let info = t.ctx.get_info().await;
assert!(info.get("database_dir").is_some()); assert!(info.get("database_dir").is_some());
} }

View File

@@ -17,7 +17,6 @@ use crate::mimeparser::*;
use crate::param::*; use crate::param::*;
use crate::peerstate::*; use crate::peerstate::*;
use crate::securejoin::{self, handle_securejoin_handshake}; use crate::securejoin::{self, handle_securejoin_handshake};
use crate::sql;
use crate::stock::StockMessage; use crate::stock::StockMessage;
use crate::{contact, location}; use crate::{contact, location};
@@ -54,7 +53,7 @@ pub async fn dc_receive_imf(
println!("{}", String::from_utf8_lossy(imf_raw)); 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 // we can not add even an empty record if we have no info whatsoever
ensure!(mime_parser.has_headers(), "No Headers Found"); 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 { 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(()) => { Ok(()) => {
context.call_cb(Event::ChatModified(chat_id)); context.call_cb(Event::ChatModified(chat_id));
} }
@@ -317,7 +316,7 @@ async fn add_parts(
// incoming non-chat messages may be discarded // incoming non-chat messages may be discarded
let mut allow_creation = true; let mut allow_creation = true;
let show_emails = 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 if mime_parser.is_system_message != SystemMessage::AutocryptSetupMessage
&& msgrmsg == MessengerMessage::No && msgrmsg == MessengerMessage::No
{ {
@@ -377,7 +376,9 @@ async fn add_parts(
} }
let (test_normal_chat_id, test_normal_chat_id_blocked) = 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, // 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 // it might also be blocked and displayed in the deaddrop as a result
@@ -399,7 +400,8 @@ async fn add_parts(
create_blocked, create_blocked,
from_id, from_id,
to_ids, to_ids,
)?; )
.await?;
*chat_id = new_chat_id; *chat_id = new_chat_id;
chat_id_blocked = new_chat_id_blocked; chat_id_blocked = new_chat_id_blocked;
if !chat_id.is_unset() if !chat_id.is_unset()
@@ -433,6 +435,7 @@ async fn add_parts(
} else if allow_creation { } else if allow_creation {
let (id, bl) = let (id, bl) =
chat::create_or_lookup_by_contact_id(context, from_id, create_blocked) chat::create_or_lookup_by_contact_id(context, from_id, create_blocked)
.await
.unwrap_or_default(); .unwrap_or_default();
*chat_id = id; *chat_id = id;
chat_id_blocked = bl; chat_id_blocked = bl;
@@ -487,7 +490,8 @@ async fn add_parts(
Blocked::Not, Blocked::Not,
from_id, from_id,
to_ids, to_ids,
)?; )
.await?;
*chat_id = new_chat_id; *chat_id = new_chat_id;
chat_id_blocked = new_chat_id_blocked; chat_id_blocked = new_chat_id_blocked;
// automatically unblock chat when the user sends a message // automatically unblock chat when the user sends a message
@@ -498,13 +502,14 @@ async fn add_parts(
} }
if chat_id.is_unset() && allow_creation { if chat_id.is_unset() && allow_creation {
let create_blocked = if MessengerMessage::No != msgrmsg let create_blocked = if MessengerMessage::No != msgrmsg
&& !Contact::is_blocked_load(context, to_id) && !Contact::is_blocked_load(context, to_id).await
{ {
Blocked::Not Blocked::Not
} else { } else {
Blocked::Deaddrop Blocked::Deaddrop
}; };
let (id, bl) = chat::create_or_lookup_by_contact_id(context, to_id, create_blocked) let (id, bl) = chat::create_or_lookup_by_contact_id(context, to_id, create_blocked)
.await
.unwrap_or_default(); .unwrap_or_default();
*chat_id = id; *chat_id = id;
chat_id_blocked = bl; chat_id_blocked = bl;
@@ -527,6 +532,7 @@ async fn add_parts(
// maybe an Autocrypt Setup Message // maybe an Autocrypt Setup Message
let (id, bl) = let (id, bl) =
chat::create_or_lookup_by_contact_id(context, DC_CONTACT_ID_SELF, Blocked::Not) chat::create_or_lookup_by_contact_id(context, DC_CONTACT_ID_SELF, Blocked::Not)
.await
.unwrap_or_default(); .unwrap_or_default();
*chat_id = id; *chat_id = id;
chat_id_blocked = bl; chat_id_blocked = bl;
@@ -554,11 +560,11 @@ async fn add_parts(
); );
// unarchive chat // unarchive chat
chat_id.unarchive(context)?; chat_id.unarchive(context).await?;
// if the mime-headers should be saved, find out its size // if the mime-headers should be saved, find out its size
// (the mime-header ends with an empty line) // (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) { if let Some(raw) = mime_parser.get(HeaderDef::InReplyTo) {
mime_in_reply_to = raw.clone(); mime_in_reply_to = raw.clone();
} }
@@ -573,18 +579,21 @@ async fn add_parts(
// (eg. one per attachment)) // (eg. one per attachment))
let icnt = mime_parser.parts.len(); 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( for part in mime_parser.parts.iter_mut() {
"INSERT INTO msgs \ let mut stmt = conn.prepare_cached(
"INSERT INTO msgs \
(rfc724_mid, server_folder, server_uid, chat_id, from_id, to_id, timestamp, \ (rfc724_mid, server_folder, server_uid, chat_id, from_id, to_id, timestamp, \
timestamp_sent, timestamp_rcvd, type, state, msgrmsg, txt, txt_raw, param, \ timestamp_sent, timestamp_rcvd, type, state, msgrmsg, txt, txt_raw, param, \
bytes, hidden, mime_headers, mime_in_reply_to, mime_references) \ bytes, hidden, mime_headers, mime_in_reply_to, mime_references) \
VALUES (?,?,?,?,?,?, ?,?,?,?,?,?, ?,?,?,?,?,?, ?,?);", 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() if mime_parser.location_kml.is_some()
&& icnt == 1 && icnt == 1
&& (part.msg == "-location-" || part.msg.is_empty()) && (part.msg == "-location-" || part.msg.is_empty())
@@ -633,14 +642,17 @@ async fn add_parts(
])?; ])?;
txt_raw = None; txt_raw = None;
// This is okay, as we use a cached prepared statement.
drop(stmt);
let row_id = 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); *insert_msg_id = MsgId::new(row_id);
created_db_entries.push((*chat_id, *insert_msg_id)); created_db_entries.push((*chat_id, *insert_msg_id));
} }
Ok(()) Ok(())
}, })
)?; .await?;
info!( info!(
context, context,
@@ -763,7 +775,7 @@ fn calc_timestamps(
/// ///
/// on success the function returns the found/created (chat_id, chat_blocked) tuple . /// on success the function returns the found/created (chat_id, chat_blocked) tuple .
#[allow(non_snake_case, clippy::cognitive_complexity)] #[allow(non_snake_case, clippy::cognitive_complexity)]
fn create_or_lookup_group( async fn create_or_lookup_group(
context: &Context, context: &Context,
mime_parser: &mut MimeMessage, mime_parser: &mut MimeMessage,
allow_creation: bool, allow_creation: bool,
@@ -827,6 +839,7 @@ fn create_or_lookup_group(
X_MrRemoveFromGrp = Some(optional_field); X_MrRemoveFromGrp = Some(optional_field);
mime_parser.is_system_message = SystemMessage::MemberRemovedFromGroup; mime_parser.is_system_message = SystemMessage::MemberRemovedFromGroup;
let left_group = Contact::lookup_id_by_addr(context, X_MrRemoveFromGrp.as_ref().unwrap()) let left_group = Contact::lookup_id_by_addr(context, X_MrRemoveFromGrp.as_ref().unwrap())
.await
== from_id as u32; == from_id as u32;
better_msg = context.stock_system_msg( better_msg = context.stock_system_msg(
if left_group { if left_group {
@@ -889,6 +902,7 @@ fn create_or_lookup_group(
// check, if we have a chat with this group ID // 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) 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)); .unwrap_or((ChatId::new(0), false, Blocked::Not));
if !chat_id.is_error() { if !chat_id.is_error() {
if chat_id_verified { if chat_id_verified {
@@ -900,7 +914,7 @@ fn create_or_lookup_group(
mime_parser.repl_msg_by_error(s); 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. // The From-address is not part of this group.
// It could be a new user or a DSN from a mailer-daemon. // 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 // 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 // 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 let self_addr = context
.get_config(Config::ConfiguredAddr) .get_config(Config::ConfiguredAddr)
.await
.unwrap_or_default(); .unwrap_or_default();
if chat_id.is_error() if chat_id.is_error()
@@ -953,7 +970,8 @@ fn create_or_lookup_group(
grpname.as_ref().unwrap(), grpname.as_ref().unwrap(),
create_blocked, create_blocked,
create_verified, create_verified,
); )
.await;
chat_id_blocked = create_blocked; chat_id_blocked = create_blocked;
recreate_member_list = true; recreate_member_list = true;
} }
@@ -997,13 +1015,14 @@ fn create_or_lookup_group(
if let Some(ref grpname) = grpname { if let Some(ref grpname) = grpname {
if grpname.len() < 200 { if grpname.len() < 200 {
info!(context, "updating grpname for chat {}", chat_id); info!(context, "updating grpname for chat {}", chat_id);
if sql::execute( if context
context, .sql
&context.sql, .execute(
"UPDATE chats SET name=? WHERE id=?;", "UPDATE chats SET name=? WHERE id=?;",
params![grpname, chat_id], params![grpname, chat_id],
) )
.is_ok() .await
.is_ok()
{ {
context.call_cb(Event::ChatModified(chat_id)); 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 { if let Some(avatar_action) = &mime_parser.group_avatar {
info!(context, "group-avatar change for {}", chat_id); 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 { match avatar_action {
AvatarAction::Change(profile_image) => { AvatarAction::Change(profile_image) => {
chat.param.set(Param::ProfileImage, profile_image); chat.param.set(Param::ProfileImage, profile_image);
@@ -1021,33 +1040,33 @@ fn create_or_lookup_group(
chat.param.remove(Param::ProfileImage); chat.param.remove(Param::ProfileImage);
} }
}; };
chat.update_param(context)?; chat.update_param(context).await?;
send_EVENT_CHAT_MODIFIED = true; send_EVENT_CHAT_MODIFIED = true;
} }
} }
// add members to group/check members // add members to group/check members
if recreate_member_list { if recreate_member_list {
if !chat::is_contact_in_chat(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); chat::add_to_chat_contacts_table(context, chat_id, DC_CONTACT_ID_SELF).await;
} }
if from_id > DC_CONTACT_ID_LAST_SPECIAL if from_id > DC_CONTACT_ID_LAST_SPECIAL
&& !Contact::addr_equals_contact(context, &self_addr, from_id as u32) && !Contact::addr_equals_contact(context, &self_addr, from_id as u32).await
&& !chat::is_contact_in_chat(context, chat_id, from_id) && !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() { for &to_id in to_ids.iter() {
info!(context, "adding to={:?} to chat id={}", to_id, chat_id); info!(context, "adding to={:?} to chat id={}", to_id, chat_id);
if !Contact::addr_equals_contact(context, &self_addr, to_id) if !Contact::addr_equals_contact(context, &self_addr, to_id).await
&& !chat::is_contact_in_chat(context, chat_id, to_id) && !chat::is_contact_in_chat(context, chat_id, to_id).await
{ {
chat::add_to_chat_contacts_table(context, chat_id, to_id); chat::add_to_chat_contacts_table(context, chat_id, to_id);
} }
} }
send_EVENT_CHAT_MODIFIED = true; send_EVENT_CHAT_MODIFIED = true;
} else if let Some(removed_addr) = X_MrRemoveFromGrp { } 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 { if contact_id != 0 {
info!(context, "remove {:?} from chat id={}", contact_id, chat_id); info!(context, "remove {:?} from chat id={}", contact_id, chat_id);
chat::remove_from_chat_contacts_table(context, chat_id, contact_id); chat::remove_from_chat_contacts_table(context, chat_id, contact_id);
@@ -1178,16 +1197,14 @@ fn create_or_lookup_adhoc_group(
Ok((new_chat_id, create_blocked)) Ok((new_chat_id, create_blocked))
} }
fn create_group_record( async fn create_group_record(
context: &Context, context: &Context,
grpid: impl AsRef<str>, grpid: impl AsRef<str>,
grpname: impl AsRef<str>, grpname: impl AsRef<str>,
create_blocked: Blocked, create_blocked: Blocked,
create_verified: VerifiedStatus, create_verified: VerifiedStatus,
) -> ChatId { ) -> ChatId {
if sql::execute( if context.sql.execute(
context,
&context.sql,
"INSERT INTO chats (type, name, grpid, blocked, created_timestamp) VALUES(?, ?, ?, ?, ?);", "INSERT INTO chats (type, name, grpid, blocked, created_timestamp) VALUES(?, ?, ?, ?, ?);",
params![ params![
if VerifiedStatus::Unverified != create_verified { if VerifiedStatus::Unverified != create_verified {
@@ -1200,7 +1217,7 @@ fn create_group_record(
create_blocked, create_blocked,
time(), time(),
], ],
) ).await
.is_err() .is_err()
{ {
warn!( warn!(
@@ -1211,7 +1228,12 @@ fn create_group_record(
); );
return ChatId::new(0); 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); let chat_id = ChatId::new(row_id);
info!( info!(
context, context,

View File

@@ -27,18 +27,18 @@ pub struct EncryptHelper {
} }
impl EncryptHelper { impl EncryptHelper {
pub fn new(context: &Context) -> Result<EncryptHelper> { pub async fn new(context: &Context) -> Result<EncryptHelper> {
let prefer_encrypt = 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(); .unwrap_or_default();
let addr = match context.get_config(Config::ConfiguredAddr) { let addr = match context.get_config(Config::ConfiguredAddr).await {
None => { None => {
bail!("addr not configured!"); bail!("addr not configured!");
} }
Some(addr) => addr, 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 { Ok(EncryptHelper {
prefer_encrypt, prefer_encrypt,
@@ -88,12 +88,12 @@ impl EncryptHelper {
} }
/// Tries to encrypt the passed in `mail`. /// Tries to encrypt the passed in `mail`.
pub fn encrypt( pub async fn encrypt(
&mut self, &mut self,
context: &Context, context: &Context,
min_verified: PeerstateVerifiedStatus, min_verified: PeerstateVerifiedStatus,
mail_to_encrypt: lettre_email::PartBuilder, mail_to_encrypt: lettre_email::PartBuilder,
peerstates: &[(Option<Peerstate>, &str)], peerstates: &[(Option<Peerstate<'_>>, &str)],
) -> Result<String> { ) -> Result<String> {
let mut keyring = Keyring::default(); let mut keyring = Keyring::default();
@@ -109,6 +109,7 @@ impl EncryptHelper {
let public_key = Key::from(self.public_key.clone()); let public_key = Key::from(self.public_key.clone());
keyring.add_ref(&public_key); keyring.add_ref(&public_key);
let sign_key = Key::from_self_private(context, self.addr.clone(), &context.sql) let sign_key = Key::from_self_private(context, self.addr.clone(), &context.sql)
.await
.ok_or_else(|| format_err!("missing own private key"))?; .ok_or_else(|| format_err!("missing own private key"))?;
let raw_message = mail_to_encrypt.build().as_string().into_bytes(); 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, context: &Context,
mail: &ParsedMail<'_>, mail: &ParsedMail<'_>,
message_time: i64, message_time: i64,
@@ -141,14 +142,14 @@ pub fn try_decrypt(
if let Some(ref mut peerstate) = peerstate { if let Some(ref mut peerstate) = peerstate {
if let Some(ref header) = autocryptheader { if let Some(ref header) = autocryptheader {
peerstate.apply_header(&header, message_time); 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) { } else if message_time > peerstate.last_seen_autocrypt && !contains_report(mail) {
peerstate.degrade_encryption(message_time); 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 { } else if let Some(ref header) = autocryptheader {
let p = Peerstate::from_header(context, header, message_time); 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); peerstate = Some(p);
} }
} }
@@ -158,16 +159,19 @@ pub fn try_decrypt(
let mut public_keyring_for_validate = Keyring::default(); let mut public_keyring_for_validate = Keyring::default();
let mut out_mail = None; let mut out_mail = None;
let mut signatures = HashSet::default(); 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 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 { if peerstate.as_ref().map(|p| p.last_seen).unwrap_or_else(|| 0) == 0 {
peerstate = Peerstate::from_addr(&context, &context.sql, &from); peerstate = Peerstate::from_addr(&context, &context.sql, &from);
} }
if let Some(ref peerstate) = peerstate { if let Some(ref peerstate) = peerstate {
if peerstate.degrade_event.is_some() { 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 { if let Some(ref key) = peerstate.gossip_key {
public_keyring_for_validate.add_ref(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 /// 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 /// only generate one key per context even when multiple threads call
/// this function concurrently. /// this function concurrently.
fn load_or_generate_self_public_key( async fn load_or_generate_self_public_key(
context: &Context, context: &Context,
self_addr: impl AsRef<str>, self_addr: impl AsRef<str>,
) -> Result<SignedPublicKey> { ) -> Result<SignedPublicKey> {
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) return SignedPublicKey::try_from(key)
.map_err(|_| Error::Message("Not a public key".into())); .map_err(|_| Error::Message("Not a public key".into()));
} }
let _guard = context.generating_key_mutex.lock().unwrap(); let _guard = context.generating_key_mutex.lock().unwrap();
// Check again in case the key was generated while we were waiting for the lock. // Check again in case the key was generated while we were waiting for the lock.
if let Some(key) = Key::from_self_public(context, &self_addr, &context.sql) { if let Some(key) = Key::from_self_public(context, &self_addr, &context.sql).await {
return SignedPublicKey::try_from(key) return SignedPublicKey::try_from(key)
.map_err(|_| Error::Message("Not a public key".into())); .map_err(|_| Error::Message("Not a public key".into()));
} }
@@ -214,10 +218,10 @@ fn load_or_generate_self_public_key(
let start = std::time::Instant::now(); let start = std::time::Instant::now();
let keygen_type = 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); info!(context, "Generating keypair with type {}", keygen_type);
let keypair = pgp::create_keypair(EmailAddress::new(self_addr.as_ref())?, 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!( info!(
context, context,
"Keypair generated in {:.3}s.", "Keypair generated in {:.3}s.",
@@ -347,14 +351,18 @@ fn contains_report(mail: &ParsedMail<'_>) -> bool {
/// ///
/// If this succeeds you are also guaranteed that the /// If this succeeds you are also guaranteed that the
/// [Config::ConfiguredAddr] is configured, this address is returned. /// [Config::ConfiguredAddr] is configured, this address is returned.
pub fn ensure_secret_key_exists(context: &Context) -> Result<String> { pub async fn ensure_secret_key_exists(context: &Context) -> Result<String> {
let self_addr = context.get_config(Config::ConfiguredAddr).ok_or_else(|| { let self_addr = context
format_err!(concat!( .get_config(Config::ConfiguredAddr)
"Failed to get self address, ", .await
"cannot ensure secret key if not configured." .ok_or_else(|| {
)) format_err!(concat!(
})?; "Failed to get self address, ",
load_or_generate_self_public_key(context, &self_addr)?; "cannot ensure secret key if not configured."
))
})?;
load_or_generate_self_public_key(context, &self_addr).await?;
Ok(self_addr) Ok(self_addr)
} }
@@ -371,7 +379,7 @@ mod tests {
async fn test_prexisting() { async fn test_prexisting() {
let t = dummy_context(); let t = dummy_context();
let test_addr = configure_alice_keypair(&t.ctx).await; let test_addr = configure_alice_keypair(&t.ctx).await;
assert_eq!(ensure_secret_key_exists(&t.ctx).unwrap(), test_addr); assert_eq!(ensure_secret_key_exists(&t.ctx).await.unwrap(), test_addr);
} }
#[test] #[test]
@@ -412,7 +420,7 @@ Sent with my Delta Chat Messenger: https://delta.chat";
async fn test_existing() { async fn test_existing() {
let t = dummy_context(); let t = dummy_context();
let addr = configure_alice_keypair(&t.ctx).await; let addr = configure_alice_keypair(&t.ctx).await;
let key = load_or_generate_self_public_key(&t.ctx, addr); let key = load_or_generate_self_public_key(&t.ctx, addr).await;
assert!(key.is_ok()); assert!(key.is_ok());
} }

View File

@@ -367,11 +367,11 @@ impl Imap {
if self.is_connected().await && !self.should_reconnect() { if self.is_connected().await && !self.should_reconnect() {
return Ok(()); 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); return Err(Error::ConnectWithoutConfigure);
} }
let param = LoginParam::from_database(context, "configured_"); let param = LoginParam::from_database(context, "configured_").await;
// the trailing underscore is correct // the trailing underscore is correct
if self.connect(context, &param).await { if self.connect(context, &param).await {
@@ -415,7 +415,7 @@ impl Imap {
let teardown = match &mut *self.session.lock().await { let teardown = match &mut *self.session.lock().await {
Some(ref mut session) => match session.capabilities().await { Some(ref mut session) => match session.capabilities().await {
Ok(caps) => { Ok(caps) => {
if !context.sql.is_open() { if !context.sql.is_open().await {
warn!(context, "IMAP-LOGIN as {} ok but ABORTING", lp.mail_user,); warn!(context, "IMAP-LOGIN as {} ok but ABORTING", lp.mail_user,);
true true
} else { } else {
@@ -465,7 +465,7 @@ impl Imap {
} }
pub async fn fetch(&self, context: &Context, watch_folder: &str) -> Result<()> { pub async fn fetch(&self, context: &Context, watch_folder: &str) -> Result<()> {
if !context.sql.is_open() { if !context.sql.is_open().await {
// probably shutdown // probably shutdown
return Err(Error::InTeardown); return Err(Error::InTeardown);
} }
@@ -477,9 +477,13 @@ impl Imap {
Ok(()) Ok(())
} }
fn get_config_last_seen_uid<S: AsRef<str>>(&self, context: &Context, folder: S) -> (u32, u32) { async fn get_config_last_seen_uid<S: AsRef<str>>(
&self,
context: &Context,
folder: S,
) -> (u32, u32) {
let key = format!("imap.mailbox.{}", folder.as_ref()); 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.<folder>=<uidvalidity>:<lastseenuid>` // the entry has the format `imap.mailbox.<folder>=<uidvalidity>:<lastseenuid>`
let mut parts = entry.split(':'); let mut parts = entry.split(':');
( (
@@ -508,7 +512,7 @@ impl Imap {
self.select_folder(context, Some(folder)).await?; self.select_folder(context, Some(folder)).await?;
// compare last seen UIDVALIDITY against the current one // 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 config = self.config.read().await;
let mailbox = config let mailbox = config
@@ -590,8 +594,8 @@ impl Imap {
context: &Context, context: &Context,
folder: S, folder: S,
) -> Result<bool> { ) -> Result<bool> {
let show_emails = let show_emails = ShowEmails::from_i32(context.get_config_int(Config::ShowEmails).await)
ShowEmails::from_i32(context.get_config_int(Config::ShowEmails)).unwrap_or_default(); .unwrap_or_default();
let (uid_validity, last_seen_uid) = self let (uid_validity, last_seen_uid) = self
.select_with_uidvalidity(context, folder.as_ref()) .select_with_uidvalidity(context, folder.as_ref())
@@ -706,7 +710,7 @@ impl Imap {
Ok(read_cnt > 0) Ok(read_cnt > 0)
} }
fn set_config_last_seen_uid<S: AsRef<str>>( async fn set_config_last_seen_uid<S: AsRef<str>>(
&self, &self,
context: &Context, context: &Context,
folder: S, folder: S,
@@ -716,7 +720,11 @@ impl Imap {
let key = format!("imap.mailbox.{}", folder.as_ref()); let key = format!("imap.mailbox.{}", folder.as_ref());
let val = format!("{}:{}", uidvalidity, lastseenuid); 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. /// Fetches a single message by server UID.
@@ -1080,7 +1088,8 @@ impl Imap {
) -> Result<()> { ) -> Result<()> {
let folders_configured = context let folders_configured = context
.sql .sql
.get_raw_config_int(context, "folders_configured"); .get_raw_config_int(context, "folders_configured")
.await;
if folders_configured.unwrap_or_default() >= 3 { if folders_configured.unwrap_or_default() >= 3 {
// the "3" here we increase if we have future updates to // the "3" here we increase if we have future updates to
// to folder configuration // to folder configuration
@@ -1168,24 +1177,28 @@ impl Imap {
} }
context context
.sql .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 { if let Some(ref mvbox_folder) = mvbox_folder {
context.sql.set_raw_config( context
context, .sql
"configured_mvbox_folder", .set_raw_config(context, "configured_mvbox_folder", Some(mvbox_folder))
Some(mvbox_folder), .await?;
)?;
} }
if let Some(ref sentbox_folder) = sentbox_folder { if let Some(ref sentbox_folder) = sentbox_folder {
context.sql.set_raw_config( context
context, .sql
"configured_sentbox_folder", .set_raw_config(
Some(sentbox_folder.name()), context,
)?; "configured_sentbox_folder",
Some(sentbox_folder.name()),
)
.await?;
} }
context context
.sql .sql
.set_raw_config_int(context, "folders_configured", 3)?; .set_raw_config_int(context, "folders_configured", 3)
.await?;
} }
info!(context, "FINISHED configuring IMAP-folders."); info!(context, "FINISHED configuring IMAP-folders.");
Ok(()) Ok(())

View File

@@ -81,7 +81,7 @@ pub async fn imex(context: &Context, what: ImexMode, param1: Option<impl AsRef<P
} }
/// Returns the filename of the backup found (otherwise an error) /// Returns the filename of the backup found (otherwise an error)
pub fn has_backup(context: &Context, dir_name: impl AsRef<Path>) -> Result<String> { pub async fn has_backup(context: &Context, dir_name: impl AsRef<Path>) -> Result<String> {
let dir_name = dir_name.as_ref(); let dir_name = dir_name.as_ref();
let dir_iter = std::fs::read_dir(dir_name)?; let dir_iter = std::fs::read_dir(dir_name)?;
let mut newest_backup_time = 0; let mut newest_backup_time = 0;
@@ -93,16 +93,17 @@ pub fn has_backup(context: &Context, dir_name: impl AsRef<Path>) -> Result<Strin
let name = name.to_string_lossy(); let name = name.to_string_lossy();
if name.starts_with("delta-chat") && name.ends_with(".bak") { if name.starts_with("delta-chat") && name.ends_with(".bak") {
let sql = Sql::new(); let sql = Sql::new();
if sql.open(context, &path, true) { if sql.open(context, &path, true).await {
let curr_backup_time = sql let curr_backup_time = sql
.get_raw_config_int(context, "backup_time") .get_raw_config_int(context, "backup_time")
.await
.unwrap_or_default(); .unwrap_or_default();
if curr_backup_time > newest_backup_time { if curr_backup_time > newest_backup_time {
newest_backup_path = Some(path); newest_backup_path = Some(path);
newest_backup_time = curr_backup_time; newest_backup_time = curr_backup_time;
} }
info!(context, "backup_time of {} is {}", name, 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<String> {
let setup_code = create_setup_code(context); let setup_code = create_setup_code(context);
/* this may require a keypair to be created. this may take a second ... */ /* this may require a keypair to be created. this may take a second ... */
ensure!(!context.shall_stop_ongoing(), "canceled"); ensure!(!context.shall_stop_ongoing(), "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 ... */ /* encrypting may also take a while ... */
ensure!(!context.shall_stop_ongoing(), "canceled"); ensure!(!context.shall_stop_ongoing(), "canceled");
let setup_file_blob = BlobObject::create( let setup_file_blob = BlobObject::create(
@@ -134,7 +135,7 @@ async fn do_initiate_key_transfer(context: &Context) -> Result<String> {
setup_file_content.as_bytes(), 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 = Message::default();
msg.viewtype = Viewtype::File; msg.viewtype = Viewtype::File;
msg.param.set(Param::File, setup_file_blob.as_name()); msg.param.set(Param::File, setup_file_blob.as_name());
@@ -152,7 +153,7 @@ async fn do_initiate_key_transfer(context: &Context) -> Result<String> {
info!(context, "Wait for setup message being sent ...",); info!(context, "Wait for setup message being sent ...",);
while !context.shall_stop_ongoing() { while !context.shall_stop_ongoing() {
std::thread::sleep(std::time::Duration::from_secs(1)); 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() { if msg.is_sent() {
info!(context, "... setup message sent.",); info!(context, "... setup message sent.",);
break; break;
@@ -170,15 +171,16 @@ async fn do_initiate_key_transfer(context: &Context) -> Result<String> {
/// Renders HTML body of a setup file message. /// Renders HTML body of a setup file message.
/// ///
/// The `passphrase` must be at least 2 characters long. /// The `passphrase` must be at least 2 characters long.
pub fn render_setup_file(context: &Context, passphrase: &str) -> Result<String> { pub async fn render_setup_file(context: &Context, passphrase: &str) -> Result<String> {
ensure!( ensure!(
passphrase.len() >= 2, passphrase.len() >= 2,
"Passphrase must be at least 2 chars long." "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) let private_key = Key::from_self_private(context, self_addr, &context.sql)
.await
.ok_or_else(|| format_err!("Failed to get private key."))?; .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, false => None,
true => Some(("Autocrypt-Prefer-Encrypt", "mutual")), true => Some(("Autocrypt-Prefer-Encrypt", "mutual")),
}; };
@@ -239,8 +241,8 @@ pub fn create_setup_code(_context: &Context) -> String {
ret ret
} }
fn maybe_add_bcc_self_device_msg(context: &Context) -> Result<()> { async fn maybe_add_bcc_self_device_msg(context: &Context) -> Result<()> {
if !context.sql.get_raw_config_bool(context, "bcc_self") { if !context.sql.get_raw_config_bool(context, "bcc_self").await {
let mut msg = Message::new(Viewtype::Text); let mut msg = Message::new(Viewtype::Text);
// TODO: define this as a stockstring once the wording is settled. // TODO: define this as a stockstring once the wording is settled.
msg.text = Some( 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\"." go to the settings and enable \"Send copy to self\"."
.to_string(), .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(()) 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"); 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!( ensure!(
msg.is_setupmessage(), msg.is_setupmessage(),
"Message is no Autocrypt Setup Message." "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 file = dc_open_file(context, filename)?;
let sc = normalize_setup_code(setup_code); let sc = normalize_setup_code(setup_code);
let armored_key = decrypt_setup_file(context, &sc, file)?; let armored_key = decrypt_setup_file(context, &sc, file)?;
set_self_key(context, &armored_key, true, true)?; set_self_key(context, &armored_key, true, true).await?;
maybe_add_bcc_self_device_msg(context)?; maybe_add_bcc_self_device_msg(context).await?;
Ok(()) Ok(())
} else { } 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, context: &Context,
armored: &str, armored: &str,
set_default: bool, set_default: bool,
@@ -301,7 +307,8 @@ fn set_self_key(
}; };
context context
.sql .sql
.set_raw_config_int(context, "e2ee_enabled", e2ee_enabled)?; .set_raw_config_int(context, "e2ee_enabled", e2ee_enabled)
.await?;
} }
None => { None => {
if prefer_encrypt_required { 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"); ensure!(self_addr.is_some(), "Missing self addr");
let addr = EmailAddress::new(&self_addr.unwrap_or_default())?; let addr = EmailAddress::new(&self_addr.unwrap_or_default())?;
@@ -331,7 +338,8 @@ fn set_self_key(
} else { } else {
key::KeyPairUse::ReadOnly key::KeyPairUse::ReadOnly
}, },
)?; )
.await?;
Ok(()) Ok(())
} }
@@ -359,8 +367,7 @@ pub fn normalize_setup_code(s: &str) -> String {
out out
} }
#[allow(non_snake_case)] pub async fn job_imex_imap(context: &Context, job: &Job) -> Result<()> {
pub fn JobImexImap(context: &Context, job: &Job) -> Result<()> {
ensure!(context.alloc_ongoing(), "could not allocate ongoing"); ensure!(context.alloc_ongoing(), "could not allocate ongoing");
let what: Option<ImexMode> = job.param.get_int(Param::Cmd).and_then(ImexMode::from_i32); let what: Option<ImexMode> = job.param.get_int(Param::Cmd).and_then(ImexMode::from_i32);
let param = job.param.get(Param::Arg).unwrap_or_default(); let param = job.param.get(Param::Arg).unwrap_or_default();
@@ -369,10 +376,10 @@ pub fn JobImexImap(context: &Context, job: &Job) -> Result<()> {
info!(context, "Import/export process started."); info!(context, "Import/export process started.");
context.call_cb(Event::ImexProgress(10)); 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) { if what == Some(ImexMode::ExportBackup) || what == Some(ImexMode::ExportSelfKeys) {
// before we export anything, make sure the private key exists // before we export anything, make sure the private key exists
if e2ee::ensure_secret_key_exists(context).is_err() { if e2ee::ensure_secret_key_exists(context).await.is_err() {
context.free_ongoing(); context.free_ongoing();
bail!("Cannot create private key or private key not available."); bail!("Cannot create private key or private key not available.");
} else { } else {
@@ -381,10 +388,10 @@ pub fn JobImexImap(context: &Context, job: &Job) -> Result<()> {
} }
let path = Path::new(param); let path = Path::new(param);
let success = match what { let success = match what {
Some(ImexMode::ExportSelfKeys) => export_self_keys(context, path), Some(ImexMode::ExportSelfKeys) => export_self_keys(context, path).await,
Some(ImexMode::ImportSelfKeys) => import_self_keys(context, path), Some(ImexMode::ImportSelfKeys) => import_self_keys(context, path).await,
Some(ImexMode::ExportBackup) => export_backup(context, path), Some(ImexMode::ExportBackup) => export_backup(context, path).await,
Some(ImexMode::ImportBackup) => import_backup(context, path), Some(ImexMode::ImportBackup) => import_backup(context, path).await,
None => { None => {
bail!("unknown IMEX type"); bail!("unknown IMEX type");
} }
@@ -404,7 +411,7 @@ pub fn JobImexImap(context: &Context, job: &Job) -> Result<()> {
} }
/// Import Backup /// Import Backup
fn import_backup(context: &Context, backup_to_import: impl AsRef<Path>) -> Result<()> { async fn import_backup(context: &Context, backup_to_import: impl AsRef<Path>) -> Result<()> {
info!( info!(
context, context,
"Import \"{}\" to \"{}\".", "Import \"{}\" to \"{}\".",
@@ -413,7 +420,7 @@ fn import_backup(context: &Context, backup_to_import: impl AsRef<Path>) -> Resul
); );
ensure!( ensure!(
!context.is_configured(), !context.is_configured().await,
"Cannot import backups to accounts in use." "Cannot import backups to accounts in use."
); );
context.sql.close(&context); context.sql.close(&context);
@@ -430,61 +437,71 @@ fn import_backup(context: &Context, backup_to_import: impl AsRef<Path>) -> Resul
/* error already logged */ /* error already logged */
/* re-open copied database file */ /* re-open copied database file */
ensure!( ensure!(
context.sql.open(&context, &context.get_dbfile(), false), context
.sql
.open(&context, &context.get_dbfile(), false)
.await,
"could not re-open db" "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 let total_files_cnt = context
.sql .sql
.query_get_value::<_, isize>(context, "SELECT COUNT(*) FROM backup_blobs;", params![]) .query_get_value::<_, isize>(context, "SELECT COUNT(*) FROM backup_blobs;", params![])
.await
.unwrap_or_default() as usize; .unwrap_or_default() as usize;
info!( info!(
context, context,
"***IMPORT-in-progress: total_files_cnt={:?}", total_files_cnt, "***IMPORT-in-progress: total_files_cnt={:?}", total_files_cnt,
); );
let res = context.sql.query_map( let res = context
"SELECT file_name, file_content FROM backup_blobs ORDER BY id;", .sql
params![], .query_map(
|row| { "SELECT file_name, file_content FROM backup_blobs ORDER BY id;",
let name: String = row.get(0)?; params![],
let blob: Vec<u8> = row.get(1)?; |row| {
let name: String = row.get(0)?;
let blob: Vec<u8> = row.get(1)?;
Ok((name, blob)) Ok((name, blob))
}, },
|files| { |files| {
for (processed_files_cnt, file) in files.enumerate() { for (processed_files_cnt, file) in files.enumerate() {
let (file_name, file_blob) = file?; let (file_name, file_blob) = file?;
if context.shall_stop_ongoing() { if context.shall_stop_ongoing() {
return Ok(false); return Ok(false);
} }
let mut permille = processed_files_cnt * 1000 / total_files_cnt; let mut permille = processed_files_cnt * 1000 / total_files_cnt;
if permille < 10 { if permille < 10 {
permille = 10 permille = 10
} }
if permille > 990 { if permille > 990 {
permille = 990 permille = 990
} }
context.call_cb(Event::ImexProgress(permille)); context.call_cb(Event::ImexProgress(permille));
if file_blob.is_empty() { if file_blob.is_empty() {
continue; continue;
} }
let path_filename = context.get_blobdir().join(file_name); let path_filename = context.get_blobdir().join(file_name);
dc_write_file(context, &path_filename, &file_blob)?; dc_write_file(context, &path_filename, &file_blob)?;
} }
Ok(true) Ok(true)
}, },
); )
.await;
match res { match res {
Ok(all_files_extracted) => { Ok(all_files_extracted) => {
if all_files_extracted { if all_files_extracted {
// only delete backup_blobs if all files were successfully extracted // only delete backup_blobs if all files were successfully extracted
sql::execute(context, &context.sql, "DROP TABLE backup_blobs;", params![])?; context
sql::try_execute(context, &context.sql, "VACUUM;").ok(); .sql
.execute("DROP TABLE backup_blobs;", params![])
.await?;
context.sql.execute("VACUUM;", params![]).await.ok();
Ok(()) Ok(())
} else { } else {
bail!("received stop signal"); bail!("received stop signal");
@@ -499,7 +516,7 @@ fn import_backup(context: &Context, backup_to_import: impl AsRef<Path>) -> Resul
******************************************************************************/ ******************************************************************************/
/* the FILE_PROGRESS macro calls the callback with the permille of files processed. /* 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. */ The macro avoids weird values of 0% or 100% while still working. */
fn export_backup(context: &Context, dir: impl AsRef<Path>) -> Result<()> { async fn export_backup(context: &Context, dir: impl AsRef<Path>) -> Result<()> {
// get a fine backup file name (the name includes the date so that multiple backup instances are possible) // 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. // 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); // let dest_path_filename = dc_get_next_backup_file(context, dir, res);
@@ -507,9 +524,9 @@ fn export_backup(context: &Context, dir: impl AsRef<Path>) -> Result<()> {
let dest_path_filename = dc_get_next_backup_path(dir, now)?; let dest_path_filename = dc_get_next_backup_path(dir, now)?;
let dest_path_string = dest_path_filename.to_string_lossy().to_string(); 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 // we close the database during the copy of the dbfile
context.sql.close(context); context.sql.close(context);
@@ -531,18 +548,20 @@ fn export_backup(context: &Context, dir: impl AsRef<Path>) -> Result<()> {
} }
let dest_sql = Sql::new(); let dest_sql = Sql::new();
ensure!( ensure!(
dest_sql.open(context, &dest_path_filename, false), dest_sql.open(context, &dest_path_filename, false).await,
"could not open exported database {}", "could not open exported database {}",
dest_path_string 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) => { Err(err) => {
dc_delete_file(context, &dest_path_filename); dc_delete_file(context, &dest_path_filename);
error!(context, "backup failed: {}", err); error!(context, "backup failed: {}", err);
Err(err) Err(err)
} }
Ok(()) => { 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)); context.call_cb(Event::ImexFileWritten(dest_path_filename));
Ok(()) Ok(())
} }
@@ -552,16 +571,15 @@ fn export_backup(context: &Context, dir: impl AsRef<Path>) -> Result<()> {
Ok(res?) 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 // 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) // the source to be locked, neigher the destination as it is used only here)
if !sql.table_exists("backup_blobs") { if !sql.table_exists("backup_blobs").await? {
sql::execute( sql.execute(
context,
&sql,
"CREATE TABLE backup_blobs (id INTEGER PRIMARY KEY, file_name, file_content);", "CREATE TABLE backup_blobs (id INTEGER PRIMARY KEY, file_name, file_content);",
params![], params![],
)? )
.await?;
} }
// copy all files from BLOBDIR into backup-db // copy all files from BLOBDIR into backup-db
let mut total_files_cnt = 0; 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(); total_files_cnt += dir_handle.filter(|r| r.is_ok()).count();
info!(context, "EXPORT: total_files_cnt={}", total_files_cnt); 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(); sql.with_conn(|conn| {
let name = name_f.to_string_lossy(); // scan directory, pass 2: copy files
if name.starts_with("delta-chat") && name.ends_with(".bak") { 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; continue;
} }
info!(context, "EXPORT: copying filename={}", name); // bail out if we can't insert
let curr_path_filename = context.get_blobdir().join(entry.file_name()); stmt.execute(params![name, buf])?;
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])?;
}
} }
Ok(true) }
}, Ok(())
)?; })
ensure!(exported_all_files, "canceled during export-files"); .await?;
Ok(()) Ok(())
} }
/******************************************************************************* /*******************************************************************************
* Classic key import * Classic key import
******************************************************************************/ ******************************************************************************/
fn import_self_keys(context: &Context, dir: impl AsRef<Path>) -> Result<()> { async fn import_self_keys(context: &Context, dir: impl AsRef<Path>) -> Result<()> {
/* hint: even if we switch to import Autocrypt Setup Files, we should leave the possibility to import /* 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. 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. 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<Path>) -> Result<()> {
match dc_read_file(context, &path_plus_name) { match dc_read_file(context, &path_plus_name) {
Ok(buf) => { Ok(buf) => {
let armored = std::string::String::from_utf8_lossy(&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); error!(context, "set_self_key: {}", err);
continue; continue;
} }
@@ -662,45 +683,48 @@ fn import_self_keys(context: &Context, dir: impl AsRef<Path>) -> Result<()> {
Ok(()) Ok(())
} }
fn export_self_keys(context: &Context, dir: impl AsRef<Path>) -> Result<()> { async fn export_self_keys(context: &Context, dir: impl AsRef<Path>) -> Result<()> {
let mut export_errors = 0; let mut export_errors = 0;
context.sql.query_map( context
"SELECT id, public_key, private_key, is_default FROM keypairs;", .sql
params![], .query_map(
|row| { "SELECT id, public_key, private_key, is_default FROM keypairs;",
let id = row.get(0)?; params![],
let public_key_blob: Vec<u8> = row.get(1)?; |row| {
let public_key = Key::from_slice(&public_key_blob, KeyType::Public); let id = row.get(0)?;
let private_key_blob: Vec<u8> = row.get(2)?; let public_key_blob: Vec<u8> = row.get(1)?;
let private_key = Key::from_slice(&private_key_blob, KeyType::Private); let public_key = Key::from_slice(&public_key_blob, KeyType::Public);
let is_default: i32 = row.get(3)?; let private_key_blob: Vec<u8> = 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)) Ok((id, public_key, private_key, is_default))
}, },
|keys| { |keys| {
for key_pair in keys { for key_pair in keys {
let (id, public_key, private_key, is_default) = key_pair?; let (id, public_key, private_key, is_default) = key_pair?;
let id = Some(id).filter(|_| is_default != 0); let id = Some(id).filter(|_| is_default != 0);
if let Some(key) = public_key { if let Some(key) = public_key {
if export_key_to_asc_file(context, &dir, id, &key).is_err() { if export_key_to_asc_file(context, &dir, id, &key).is_err() {
export_errors += 1;
}
} else {
export_errors += 1; export_errors += 1;
} }
} else { if let Some(key) = private_key {
export_errors += 1; if export_key_to_asc_file(context, &dir, id, &key).is_err() {
} export_errors += 1;
if let Some(key) = private_key { }
if export_key_to_asc_file(context, &dir, id, &key).is_err() { } else {
export_errors += 1; export_errors += 1;
} }
} else {
export_errors += 1;
} }
}
Ok(()) Ok(())
}, },
)?; )
.await?;
ensure!(export_errors == 0, "errors while exporting keys"); ensure!(export_errors == 0, "errors while exporting keys");
Ok(()) Ok(())
@@ -745,7 +769,7 @@ mod tests {
let t = test_context(Some(Box::new(logging_cb))); let t = test_context(Some(Box::new(logging_cb)));
configure_alice_keypair(&t.ctx).await; 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); println!("{}", &msg);
// Check some substrings, indicating things got substituted. // Check some substrings, indicating things got substituted.
// In particular note the mixing of `\r\n` and `\n` depending // 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()) .set_stock_translation(StockMessage::AcSetupMsgBody, "hello\r\nthere".to_string())
.unwrap(); .unwrap();
configure_alice_keypair(&t.ctx).await; 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); println!("{}", &msg);
assert!(msg.contains("<p>hello<br>there</p>")); assert!(msg.contains("<p>hello<br>there</p>"));
} }

View File

@@ -150,6 +150,7 @@ impl Job {
context context
.sql .sql
.execute("DELETE FROM jobs WHERE id=?;", params![self.job_id as i32]) .execute("DELETE FROM jobs WHERE id=?;", params![self.job_id as i32])
.await
.is_ok() .is_ok()
} }
@@ -157,18 +158,19 @@ impl Job {
/// ///
/// To add a new job, use [job_add]. /// To add a new job, use [job_add].
async fn update(&self, context: &Context) -> bool { async fn update(&self, context: &Context) -> bool {
sql::execute( context
context, .sql
&context.sql, .execute(
"UPDATE jobs SET desired_timestamp=?, tries=?, param=? WHERE id=?;", "UPDATE jobs SET desired_timestamp=?, tries=?, param=? WHERE id=?;",
params![ params![
self.desired_timestamp, self.desired_timestamp,
self.tries as i64, self.tries as i64,
self.param.to_string(), self.param.to_string(),
self.job_id as i32, self.job_id as i32,
], ],
) )
.is_ok() .await
.is_ok()
} }
async fn smtp_send<F, Fut>( async fn smtp_send<F, Fut>(
@@ -247,7 +249,7 @@ impl Job {
async fn send_msg_to_smtp(&mut self, context: &Context) -> Status { async fn send_msg_to_smtp(&mut self, context: &Context) -> Status {
// connect to SMTP server, if not yet done // connect to SMTP server, if not yet done
if !context.smtp.is_connected().await { 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 { if let Err(err) = context.smtp.connect(context, &loginparam).await {
warn!(context, "SMTP connection failure: {:?}", err); warn!(context, "SMTP connection failure: {:?}", err);
return Status::RetryLater; return Status::RetryLater;
@@ -310,32 +312,35 @@ impl Job {
contact_id: u32, contact_id: u32,
) -> sql::Result<(Vec<u32>, Vec<String>)> { ) -> sql::Result<(Vec<u32>, Vec<String>)> {
// Extract message IDs from job parameters // Extract message IDs from job parameters
let res: Vec<(u32, MsgId)> = context.sql.query_map( let res: Vec<(u32, MsgId)> = context
"SELECT id, param FROM jobs WHERE foreign_id=? AND id!=?", .sql
params![contact_id, self.job_id], .query_map(
|row| { "SELECT id, param FROM jobs WHERE foreign_id=? AND id!=?",
let job_id: u32 = row.get(0)?; params![contact_id, self.job_id],
let params_str: String = row.get(1)?; |row| {
let params: Params = params_str.parse().unwrap_or_default(); let job_id: u32 = row.get(0)?;
Ok((job_id, params)) let params_str: String = row.get(1)?;
}, let params: Params = params_str.parse().unwrap_or_default();
|jobs| { Ok((job_id, params))
let res = jobs },
.filter_map(|row| { |jobs| {
let (job_id, params) = row.ok()?; let res = jobs
let msg_id = params.get_msg_id()?; .filter_map(|row| {
Some((job_id, msg_id)) let (job_id, params) = row.ok()?;
}) let msg_id = params.get_msg_id()?;
.collect(); Some((job_id, msg_id))
Ok(res) })
}, .collect();
)?; Ok(res)
},
)
.await?;
// Load corresponding RFC724 message IDs // Load corresponding RFC724 message IDs
let mut job_ids = Vec::new(); let mut job_ids = Vec::new();
let mut rfc724_mids = Vec::new(); let mut rfc724_mids = Vec::new();
for (job_id, msg_id) in res { 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); job_ids.push(job_id);
rfc724_mids.push(rfc724_mid); rfc724_mids.push(rfc724_mid);
} }
@@ -344,14 +349,14 @@ impl Job {
} }
async fn send_mdn(&mut self, context: &Context) -> Status { 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 // User has disabled MDNs after job scheduling but before
// execution. // execution.
return Status::Finished(Err(format_err!("MDNs are disabled"))); return Status::Finished(Err(format_err!("MDNs are disabled")));
} }
let contact_id = self.foreign_id; 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() { if contact.is_blocked() {
return Status::Finished(Err(format_err!("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 mimefactory = job_try!(MimeFactory::from_mdn(context, &msg, additional_rfc724_mids));
let rendered_msg = job_try!(mimefactory.render()); let rendered_msg = job_try!(mimefactory.render());
let body = rendered_msg.message; let body = rendered_msg.message;
@@ -391,7 +396,7 @@ impl Job {
// connect to SMTP server, if not yet done // connect to SMTP server, if not yet done
if !context.smtp.is_connected().await { 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 { if let Err(err) = context.smtp.connect(context, &loginparam).await {
warn!(context, "SMTP connection failure: {:?}", err); warn!(context, "SMTP connection failure: {:?}", err);
return Status::RetryLater; return Status::RetryLater;
@@ -411,7 +416,7 @@ impl Job {
async fn move_msg(&mut self, context: &Context) -> Status { async fn move_msg(&mut self, context: &Context) -> Status {
let imap_inbox = &context.inbox_thread.imap; 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 { if let Err(err) = imap_inbox.ensure_configured_folders(context, true).await {
warn!(context, "could not configure folders: {:?}", err); warn!(context, "could not configure folders: {:?}", err);
@@ -419,7 +424,8 @@ impl Job {
} }
let dest_folder = context let dest_folder = context
.sql .sql
.get_raw_config(context, "configured_mvbox_folder"); .get_raw_config(context, "configured_mvbox_folder")
.await;
if let Some(dest_folder) = dest_folder { if let Some(dest_folder) = dest_folder {
let server_folder = msg.server_folder.as_ref().unwrap(); 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 { async fn delete_msg_on_imap(&mut self, context: &Context) -> Status {
let imap_inbox = &context.inbox_thread.imap; 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 !msg.rfc724_mid.is_empty() {
if message::rfc724_mid_cnt(context, &msg.rfc724_mid) > 1 { if message::rfc724_mid_cnt(context, &msg.rfc724_mid) > 1 {
@@ -488,6 +494,7 @@ impl Job {
if let Some(mvbox_folder) = context if let Some(mvbox_folder) = context
.sql .sql
.get_raw_config(context, "configured_mvbox_folder") .get_raw_config(context, "configured_mvbox_folder")
.await
{ {
imap_inbox.empty_folder(context, &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 { async fn markseen_msg_on_imap(&mut self, context: &Context) -> Status {
let imap_inbox = &context.inbox_thread.imap; 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(); let folder = msg.server_folder.as_ref().unwrap();
match imap_inbox.set_seen(context, folder, msg.server_uid).await { 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 // The job will not be retried so locally
// there is no risk of double-sending MDNs. // there is no risk of double-sending MDNs.
if msg.param.get_bool(Param::WantsMdn).unwrap_or_default() 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 { if let Err(err) = send_mdn(context, &msg).await {
warn!(context, "could not send out mdn for {}: {}", msg.id, err); warn!(context, "could not send out mdn for {}: {}", msg.id, err);
@@ -543,7 +550,8 @@ impl Job {
} }
let dest_folder = context let dest_folder = context
.sql .sql
.get_raw_config(context, "configured_mvbox_folder"); .get_raw_config(context, "configured_mvbox_folder")
.await;
if let Some(dest_folder) = dest_folder { if let Some(dest_folder) = dest_folder {
let mut dest_uid = 0; let mut dest_uid = 0;
if ImapActionResult::RetryLater if ImapActionResult::RetryLater
@@ -566,67 +574,70 @@ impl Job {
/// Delete all pending jobs with the given action. /// Delete all pending jobs with the given action.
pub async fn kill_action(context: &Context, action: Action) -> bool { pub async fn kill_action(context: &Context, action: Action) -> bool {
sql::execute( context
context, .sql
&context.sql, .execute("DELETE FROM jobs WHERE action=?;", params![action])
"DELETE FROM jobs WHERE action=?;", .await
params![action], .is_ok()
)
.is_ok()
} }
/// Remove jobs with specified IDs. /// Remove jobs with specified IDs.
pub async fn kill_ids(context: &Context, job_ids: &[u32]) -> sql::Result<()> { pub async fn kill_ids(context: &Context, job_ids: &[u32]) -> sql::Result<()> {
sql::execute( context
context, .sql
&context.sql, .execute(
format!( format!(
"DELETE FROM jobs WHERE id IN({})", "DELETE FROM jobs WHERE id IN({})",
job_ids.iter().map(|_| "?").join(",") job_ids.iter().map(|_| "?").join(",")
), ),
job_ids, job_ids,
) )
.await?;
Ok(())
} }
pub async fn perform_inbox_fetch(context: &Context) { 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; context.inbox_thread.fetch(context, use_network).await;
} }
pub async fn perform_mvbox_fetch(context: &Context) { 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; context.mvbox_thread.fetch(context, use_network).await;
} }
pub async fn perform_sentbox_fetch(context: &Context) { 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; context.sentbox_thread.fetch(context, use_network).await;
} }
pub async fn perform_inbox_idle(context: &Context) { 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!( info!(
context, context,
"INBOX-IDLE will not be started because of waiting jobs." "INBOX-IDLE will not be started because of waiting jobs."
); );
return; 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; context.inbox_thread.idle(context, use_network).await;
} }
pub async fn perform_mvbox_idle(context: &Context) { 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; context.mvbox_thread.idle(context, use_network).await;
} }
pub async fn perform_sentbox_idle(context: &Context) { 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; 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 // 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. // but we flag it for checking jobs so that idle will be skipped.
if !context.inbox_thread.try_interrupt_idle(context).await { 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"); warn!(context, "could not interrupt idle");
} }
} }
@@ -687,7 +700,7 @@ pub async fn perform_smtp_idle(context: &Context) {
); );
} }
PerformJobsNeeded::Not | PerformJobsNeeded::AvoidDos => { 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(); 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.",); 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 let t: i64 = context
.sql .sql
.query_get_value( .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=?;", "SELECT MIN(desired_timestamp) FROM jobs WHERE thread=?;",
params![thread], params![thread],
) )
.await
.unwrap_or_default(); .unwrap_or_default();
let mut wakeup_time = time::Duration::new(10 * 60, 0); 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) { pub async fn maybe_network(context: &Context) {
{ {
context.smtp.state.write().await.probe_network = true; 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; interrupt_smtp_idle(context).await;
@@ -731,14 +747,15 @@ pub async fn maybe_network(context: &Context) {
interrupt_sentbox_idle(context).await; 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 context
.sql .sql
.exists("SELECT id FROM jobs WHERE action=?;", params![action]) .exists("SELECT id FROM jobs WHERE action=?;", params![action])
.await
.unwrap_or_default() .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); message::update_msg_state(context, msg_id, MessageState::OutDelivered);
let chat_id: ChatId = context let chat_id: ChatId = context
.sql .sql
@@ -747,19 +764,20 @@ fn set_delivered(context: &Context, msg_id: MsgId) {
"SELECT chat_id FROM msgs WHERE id=?", "SELECT chat_id FROM msgs WHERE id=?",
params![msg_id], params![msg_id],
) )
.await
.unwrap_or_default(); .unwrap_or_default();
context.call_cb(Event::MsgDelivered { chat_id, msg_id }); context.call_cb(Event::MsgDelivered { chat_id, msg_id });
} }
// special case for DC_JOB_SEND_MSG_TO_SMTP // special case for DC_JOB_SEND_MSG_TO_SMTP
pub async fn send_msg(context: &Context, msg_id: MsgId) -> Result<()> { pub async fn send_msg(context: &Context, msg_id: MsgId) -> Result<()> {
let mut msg = Message::load_from_db(context, msg_id)?; let mut msg = Message::load_from_db(context, msg_id).await?;
msg.try_calc_and_set_dimensions(context).ok(); msg.try_calc_and_set_dimensions(context).ok();
/* create message */ /* create message */
let needs_encryption = msg.param.get_bool(Param::GuaranteeE2ee).unwrap_or_default(); let needs_encryption = msg.param.get_bool(Param::GuaranteeE2ee).unwrap_or_default();
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, Ok(attach_selfavatar) => attach_selfavatar,
Err(err) => { Err(err) => {
warn!(context, "job: cannot get selfavatar-state: {}", 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 let from = context
.get_config(Config::ConfiguredAddr) .get_config(Config::ConfiguredAddr)
.await
.unwrap_or_default(); .unwrap_or_default();
let lowercase_from = from.to_lowercase(); let lowercase_from = from.to_lowercase();
if context.get_config_bool(Config::BccSelf) if context.get_config_bool(Config::BccSelf).await
&& !recipients && !recipients
.iter() .iter()
.any(|x| x.to_lowercase() == lowercase_from) .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 { 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 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); error!(context, "Failed to set kml sent_timestamp: {:?}", err);
} }
if !msg.hidden { if !msg.hidden {
if let Err(err) = if let Err(err) =
location::set_msg_location_id(context, msg.id, rendered_msg.last_added_location_id) location::set_msg_location_id(context, msg.id, rendered_msg.last_added_location_id)
.await
{ {
error!(context, "Failed to set msg_location_id: {:?}", err); 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 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); 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) { pub async fn perform_inbox_jobs(context: &Context) {
info!(context, "dc_perform_inbox_jobs starting.",); info!(context, "dc_perform_inbox_jobs starting.",);
let probe_imap_network = *context.probe_imap_network.clone().read().unwrap(); let probe_imap_network = context
*context.probe_imap_network.write().unwrap() = false; .probe_imap_network
*context.perform_inbox_jobs_needed.write().unwrap() = false; .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; job_perform(context, Thread::Imap, probe_imap_network).await;
info!(context, "dc_perform_inbox_jobs ended.",); 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) { 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); info!(context, "{}-job {} started...", thread, job);
// some configuration jobs are "exclusive": // some configuration jobs are "exclusive":
@@ -891,77 +917,77 @@ async fn job_perform(context: &Context, thread: Thread, probe_network: bool) {
x => x, x => x,
}; };
if Action::ConfigureImap == job.action || Action::ImexImap == job.action { // if Action::ConfigureImap == job.action || Action::ImexImap == job.action {
context.sentbox_thread.unsuspend(context).await; // context.sentbox_thread.unsuspend(context).await;
context.mvbox_thread.unsuspend(context).await; // context.mvbox_thread.unsuspend(context).await;
suspend_smtp_thread(context, false).await; // suspend_smtp_thread(context, false).await;
break; // break;
} // }
match try_res { // match try_res {
Status::RetryNow | Status::RetryLater => { // Status::RetryNow | Status::RetryLater => {
let tries = job.tries + 1; // let tries = job.tries + 1;
if tries < JOB_RETRIES { // if tries < JOB_RETRIES {
info!( // info!(
context, // context,
"{} thread increases job {} tries to {}", thread, job, tries // "{} thread increases job {} tries to {}", thread, job, tries
); // );
job.tries = tries; // job.tries = tries;
let time_offset = get_backoff_time_offset(tries); // let time_offset = get_backoff_time_offset(tries);
job.desired_timestamp = time() + time_offset; // job.desired_timestamp = time() + time_offset;
job.update(context).await; // job.update(context).await;
info!( // info!(
context, // context,
"{}-job #{} not succeeded on try #{}, retry in {} seconds.", // "{}-job #{} not succeeded on try #{}, retry in {} seconds.",
thread, // thread,
job.job_id as u32, // job.job_id as u32,
tries, // tries,
time_offset // time_offset
); // );
if thread == Thread::Smtp && tries < JOB_RETRIES - 1 { // if thread == Thread::Smtp && tries < JOB_RETRIES - 1 {
context.smtp.state.write().await.perform_jobs_needed = // context.smtp.state.write().await.perform_jobs_needed =
PerformJobsNeeded::AvoidDos; // PerformJobsNeeded::AvoidDos;
} // }
} else { // } else {
info!( // info!(
context, // context,
"{} thread removes job {} as it exhausted {} retries", // "{} thread removes job {} as it exhausted {} retries",
thread, // thread,
job, // job,
JOB_RETRIES // JOB_RETRIES
); // );
if job.action == Action::SendMsgToSmtp { // if job.action == Action::SendMsgToSmtp {
message::set_msg_failed( // message::set_msg_failed(
context, // context,
MsgId::new(job.foreign_id), // MsgId::new(job.foreign_id),
job.pending_error.as_ref(), // job.pending_error.as_ref(),
); // );
} // }
job.delete(context).await; // job.delete(context).await;
} // }
if !probe_network { // if !probe_network {
continue; // continue;
} // }
// on dc_maybe_network() we stop trying here; // // on dc_maybe_network() we stop trying here;
// these jobs are already tried once. // // these jobs are already tried once.
// otherwise, we just continue with the next job // // otherwise, we just continue with the next job
// to give other jobs a chance being tried at least once. // // to give other jobs a chance being tried at least once.
break; // break;
} // }
Status::Finished(res) => { // Status::Finished(res) => {
if let Err(err) = res { // if let Err(err) = res {
warn!( // warn!(
context, // context,
"{} removes job {} as it failed with error {:?}", thread, job, err // "{} removes job {} as it failed with error {:?}", thread, job, err
); // );
} else { // } else {
info!(context, "{} removes job {} as it succeeded", thread, job); // 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::MoveMsg => job.move_msg(context).await,
Action::SendMdn => job.send_mdn(context).await, Action::SendMdn => job.send_mdn(context).await,
Action::ConfigureImap => job_configure_imap(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(())), Ok(()) => Status::Finished(Ok(())),
Err(err) => { Err(err) => {
error!(context, "{}", err); error!(context, "{}", err);
@@ -994,7 +1020,9 @@ async fn perform_job_action(
} }
}, },
Action::MaybeSendLocations => location::job_maybe_send_locations(context, &job).await, 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 => { Action::Housekeeping => {
sql::housekeeping(context); sql::housekeeping(context);
Status::Finished(Ok(())) Status::Finished(Ok(()))
@@ -1078,9 +1106,7 @@ pub async fn add(
let timestamp = time(); let timestamp = time();
let thread: Thread = action.into(); let thread: Thread = action.into();
sql::execute( context.sql.execute(
context,
&context.sql,
"INSERT INTO jobs (added_timestamp, thread, action, foreign_id, param, desired_timestamp) VALUES (?,?,?,?,?,?);", "INSERT INTO jobs (added_timestamp, thread, action, foreign_id, param, desired_timestamp) VALUES (?,?,?,?,?,?);",
params![ params![
timestamp, timestamp,
@@ -1090,7 +1116,7 @@ pub async fn add(
param.to_string(), param.to_string(),
(timestamp + delay_seconds as i64) (timestamp + delay_seconds as i64)
] ]
).ok(); ).await.ok();
match thread { match thread {
Thread::Imap => interrupt_inbox_idle(context).await, 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 /// IMAP jobs. The `probe_network` parameter decides how to query
/// jobs, this is tricky and probably wrong currently. Look at the /// jobs, this is tricky and probably wrong currently. Look at the
/// SQL queries for details. /// SQL queries for details.
fn load_next_job(context: &Context, thread: Thread, probe_network: bool) -> Option<Job> { async fn load_next_job(context: &Context, thread: Thread, probe_network: bool) -> Option<Job> {
let query = if !probe_network { let query = if !probe_network {
// processing for first-try and after backoff-timeouts: // processing for first-try and after backoff-timeouts:
// process jobs in the order they were added. // 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) Ok(None)
}, },
) )
.await
.unwrap_or_default() .unwrap_or_default()
} }

View File

@@ -115,7 +115,7 @@ impl JobThread {
let prefix = format!("{}-fetch", self.name); let prefix = format!("{}-fetch", self.name);
match self.imap.connect_configured(context).await { match self.imap.connect_configured(context).await {
Ok(()) => { 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(); let start = std::time::Instant::now();
info!(context, "{} started...", prefix); info!(context, "{} started...", prefix);
let res = self let res = self
@@ -135,8 +135,12 @@ impl JobThread {
} }
} }
fn get_watch_folder(&self, context: &Context) -> Option<String> { async fn get_watch_folder(&self, context: &Context) -> Option<String> {
match context.sql.get_raw_config(context, self.folder_config_name) { match context
.sql
.get_raw_config(context, self.folder_config_name)
.await
{
Some(name) => Some(name), Some(name) => Some(name),
None => { None => {
if self.folder_config_name == "configured_inbox_folder" { if self.folder_config_name == "configured_inbox_folder" {
@@ -184,7 +188,7 @@ impl JobThread {
if !self.imap.can_idle().await { if !self.imap.can_idle().await {
true // we have to do fake_idle true // we have to do fake_idle
} else { } else {
let watch_folder = self.get_watch_folder(context); let watch_folder = self.get_watch_folder(context).await;
info!(context, "{} started...", prefix); info!(context, "{} started...", prefix);
let res = self.imap.idle(context, watch_folder).await; let res = self.imap.idle(context, watch_folder).await;
info!(context, "{} ended...", prefix); info!(context, "{} ended...", prefix);
@@ -205,7 +209,7 @@ impl JobThread {
} }
}; };
if do_fake_idle { 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; self.imap.fake_idle(context, watch_folder).await;
} }

View File

@@ -197,7 +197,7 @@ impl Key {
} }
} }
pub fn from_self_public( pub async fn from_self_public(
context: &Context, context: &Context,
self_addr: impl AsRef<str>, self_addr: impl AsRef<str>,
sql: &Sql, sql: &Sql,
@@ -209,10 +209,11 @@ impl Key {
"SELECT public_key FROM keypairs WHERE addr=? AND is_default=1;", "SELECT public_key FROM keypairs WHERE addr=? AND is_default=1;",
&[addr], &[addr],
) )
.await
.and_then(|blob: Vec<u8>| Self::from_slice(&blob, KeyType::Public)) .and_then(|blob: Vec<u8>| Self::from_slice(&blob, KeyType::Public))
} }
pub fn from_self_private( pub async fn from_self_private(
context: &Context, context: &Context,
self_addr: impl AsRef<str>, self_addr: impl AsRef<str>,
sql: &Sql, sql: &Sql,
@@ -222,6 +223,7 @@ impl Key {
"SELECT private_key FROM keypairs WHERE addr=? AND is_default=1;", "SELECT private_key FROM keypairs WHERE addr=? AND is_default=1;",
&[self_addr.as_ref()], &[self_addr.as_ref()],
) )
.await
.and_then(|blob: Vec<u8>| Self::from_slice(&blob, KeyType::Private)) .and_then(|blob: Vec<u8>| Self::from_slice(&blob, KeyType::Private))
} }
@@ -347,7 +349,7 @@ impl SaveKeyError {
/// same key again overwrites it. /// same key again overwrites it.
/// ///
/// [Config::ConfiguredAddr]: crate::config::Config::ConfiguredAddr /// [Config::ConfiguredAddr]: crate::config::Config::ConfiguredAddr
pub fn store_self_keypair( pub async fn store_self_keypair(
context: &Context, context: &Context,
keypair: &KeyPair, keypair: &KeyPair,
default: KeyPairUse, default: KeyPairUse,
@@ -368,11 +370,13 @@ pub fn store_self_keypair(
"DELETE FROM keypairs WHERE public_key=? OR private_key=?;", "DELETE FROM keypairs WHERE public_key=? OR private_key=?;",
params![public_key, secret_key], params![public_key, secret_key],
) )
.await
.map_err(|err| SaveKeyError::new("failed to remove old use of key", err))?; .map_err(|err| SaveKeyError::new("failed to remove old use of key", err))?;
if default == KeyPairUse::Default { if default == KeyPairUse::Default {
context context
.sql .sql
.execute("UPDATE keypairs SET is_default=0;", params![]) .execute("UPDATE keypairs SET is_default=0;", params![])
.await
.map_err(|err| SaveKeyError::new("failed to clear default", err))?; .map_err(|err| SaveKeyError::new("failed to clear default", err))?;
} }
let is_default = match default { let is_default = match default {
@@ -392,6 +396,7 @@ pub fn store_self_keypair(
time() time()
], ],
) )
.await
.map(|_| ()) .map(|_| ())
.map_err(|err| SaveKeyError::new("failed to insert keypair", err)) .map_err(|err| SaveKeyError::new("failed to insert keypair", err))
} }

View File

@@ -27,7 +27,7 @@ impl<'a> Keyring<'a> {
&self.keys &self.keys
} }
pub fn load_self_private_for_decrypting( pub async fn load_self_private_for_decrypting(
&mut self, &mut self,
context: &Context, context: &Context,
self_addr: impl AsRef<str>, self_addr: impl AsRef<str>,
@@ -38,6 +38,7 @@ impl<'a> Keyring<'a> {
"SELECT private_key FROM keypairs ORDER BY addr=? DESC, is_default DESC;", "SELECT private_key FROM keypairs ORDER BY addr=? DESC, is_default DESC;",
&[self_addr.as_ref()], &[self_addr.as_ref()],
) )
.await
.and_then(|blob: Vec<u8>| Key::from_slice(&blob, KeyType::Private)) .and_then(|blob: Vec<u8>| Key::from_slice(&blob, KeyType::Private))
.map(|key| self.add_owned(key)) .map(|key| self.add_owned(key))
.is_some() .is_some()

View File

@@ -15,7 +15,6 @@ use crate::job::{self, Job};
use crate::message::{Message, MsgId}; use crate::message::{Message, MsgId};
use crate::mimeparser::SystemMessage; use crate::mimeparser::SystemMessage;
use crate::param::*; use crate::param::*;
use crate::sql;
use crate::stock::StockMessage; use crate::stock::StockMessage;
/// Location record /// Location record
@@ -195,21 +194,22 @@ impl Kml {
pub async fn send_locations_to_chat(context: &Context, chat_id: ChatId, seconds: i64) { pub async fn send_locations_to_chat(context: &Context, chat_id: ChatId, seconds: i64) {
let now = time(); let now = time();
if !(seconds < 0 || chat_id.is_special()) { if !(seconds < 0 || chat_id.is_special()) {
let is_sending_locations_before = is_sending_locations_to_chat(context, chat_id); let is_sending_locations_before = is_sending_locations_to_chat(context, chat_id).await;
if sql::execute( if context
context, .sql
&context.sql, .execute(
"UPDATE chats \ "UPDATE chats \
SET locations_send_begin=?, \ SET locations_send_begin=?, \
locations_send_until=? \ locations_send_until=? \
WHERE id=?", WHERE id=?",
params![ params![
if 0 != seconds { now } else { 0 }, if 0 != seconds { now } else { 0 },
if 0 != seconds { now + seconds } else { 0 }, if 0 != seconds { now + seconds } else { 0 },
chat_id, chat_id,
], ],
) )
.is_ok() .await
.is_ok()
{ {
if 0 != seconds && !is_sending_locations_before { if 0 != seconds && !is_sending_locations_before {
let mut msg = Message::new(Viewtype::Text); let mut msg = Message::new(Viewtype::Text);
@@ -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) { 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( job::add(
context, context,
job::Action::MaybeSendLocations, 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 context
.sql .sql
.exists( .exists(
"SELECT id FROM chats WHERE (? OR id=?) AND locations_send_until>?;", "SELECT id FROM chats WHERE (? OR id=?) AND locations_send_until>?;",
params![if chat_id.is_unset() { 1 } else { 0 }, chat_id, time()], params![if chat_id.is_unset() { 1 } else { 0 }, chat_id, time()],
) )
.await
.unwrap_or_default() .unwrap_or_default()
} }
@@ -269,12 +270,16 @@ pub async fn set(context: &Context, latitude: f64, longitude: f64, accuracy: f64
} }
let mut continue_streaming = false; let mut continue_streaming = false;
if let Ok(chats) = context.sql.query_map( if let Ok(chats) = context
"SELECT id FROM chats WHERE locations_send_until>?;", .sql
params![time()], .query_map(
|row| row.get::<_, i32>(0), "SELECT id FROM chats WHERE locations_send_until>?;",
|chats| chats.collect::<Result<Vec<_>, _>>().map_err(Into::into), params![time()],
) { |row| row.get::<_, i32>(0),
|chats| chats.collect::<Result<Vec<_>, _>>().map_err(Into::into),
)
.await
{
for chat_id in chats { for chat_id in chats {
if let Err(err) = context.sql.execute( if let Err(err) = context.sql.execute(
"INSERT INTO locations \ "INSERT INTO locations \
@@ -287,7 +292,7 @@ pub async fn set(context: &Context, latitude: f64, longitude: f64, accuracy: f64
chat_id, chat_id,
DC_CONTACT_ID_SELF, DC_CONTACT_ID_SELF,
] ]
) { ).await {
warn!(context, "failed to store location {:?}", err); warn!(context, "failed to store location {:?}", err);
} else { } else {
continue_streaming = true; continue_streaming = true;
@@ -302,7 +307,7 @@ pub async fn set(context: &Context, latitude: f64, longitude: f64, accuracy: f64
continue_streaming continue_streaming
} }
pub fn get_range( pub async fn get_range(
context: &Context, context: &Context,
chat_id: ChatId, chat_id: ChatId,
contact_id: u32, contact_id: u32,
@@ -360,6 +365,7 @@ pub fn get_range(
Ok(ret) Ok(ret)
}, },
) )
.await
.unwrap_or_default() .unwrap_or_default()
} }
@@ -368,17 +374,21 @@ fn is_marker(txt: &str) -> bool {
} }
/// Deletes all locations from the database. /// Deletes all locations from the database.
pub fn delete_all(context: &Context) -> Result<(), Error> { pub async fn delete_all(context: &Context) -> Result<(), Error> {
sql::execute(context, &context.sql, "DELETE FROM locations;", params![])?; context
.sql
.execute("DELETE FROM locations;", params![])
.await?;
context.call_cb(Event::LocationChanged(None)); context.call_cb(Event::LocationChanged(None));
Ok(()) 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 mut last_added_location_id = 0;
let self_addr = context let self_addr = context
.get_config(Config::ConfiguredAddr) .get_config(Config::ConfiguredAddr)
.await
.unwrap_or_default(); .unwrap_or_default();
let (locations_send_begin, locations_send_until, locations_last_sent) = context.sql.query_row( let (locations_send_begin, locations_send_until, locations_last_sent) = context.sql.query_row(
@@ -389,7 +399,8 @@ pub fn get_kml(context: &Context, chat_id: ChatId) -> Result<(String, u32), Erro
let last_sent: i64 = row.get(2)?; let last_sent: i64 = row.get(2)?;
Ok((send_begin, send_until, last_sent)) Ok((send_begin, send_until, last_sent))
})?; })
.await?;
let now = time(); let now = time();
let mut location_count = 0; let mut location_count = 0;
@@ -433,7 +444,7 @@ pub fn get_kml(context: &Context, chat_id: ChatId) -> Result<(String, u32), Erro
} }
Ok(()) Ok(())
} }
)?; ).await?;
ret += "</Document>\n</kml>"; ret += "</Document>\n</kml>";
} }
@@ -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, context: &Context,
chat_id: ChatId, chat_id: ChatId,
timestamp: i64, timestamp: i64,
) -> Result<(), Error> { ) -> Result<(), Error> {
sql::execute( context
context, .sql
&context.sql, .execute(
"UPDATE chats SET locations_last_sent=? WHERE id=?;", "UPDATE chats SET locations_last_sent=? WHERE id=?;",
params![timestamp, chat_id], params![timestamp, chat_id],
)?; )
.await?;
Ok(()) Ok(())
} }
pub fn set_msg_location_id( pub async fn set_msg_location_id(
context: &Context, context: &Context,
msg_id: MsgId, msg_id: MsgId,
location_id: u32, location_id: u32,
) -> Result<(), Error> { ) -> Result<(), Error> {
sql::execute( context
context, .sql
&context.sql, .execute(
"UPDATE msgs SET location_id=? WHERE id=?;", "UPDATE msgs SET location_id=? WHERE id=?;",
params![location_id, msg_id], params![location_id, msg_id],
)?; )
.await?;
Ok(()) Ok(())
} }
pub fn save( pub async fn save(
context: &Context, context: &Context,
chat_id: ChatId, chat_id: ChatId,
contact_id: u32, contact_id: u32,
@@ -504,50 +516,56 @@ pub fn save(
independent: bool, independent: bool,
) -> Result<u32, Error> { ) -> Result<u32, Error> {
ensure!(!chat_id.is_special(), "Invalid chat id"); ensure!(!chat_id.is_special(), "Invalid chat id");
context
let newest_location_id = context
.sql .sql
.prepare2( .with_conn(|mut conn| {
"SELECT id FROM locations WHERE timestamp=? AND from_id=?", let mut newest_timestamp = 0;
"INSERT INTO locations\ 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) \ (timestamp, from_id, chat_id, latitude, longitude, accuracy, independent) \
VALUES (?,?,?,?,?,?,?);", 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 { if independent || !exists {
stmt_insert.execute(params![ 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, location.timestamp,
"from_id",
contact_id as i32, 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) }
}, Ok(newest_location_id)
) })
.map_err(Into::into) .await?;
Ok(newest_location_id)
} }
pub(crate) async fn job_maybe_send_locations(context: &Context, _job: &Job) -> job::Status { 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 -------------- ", " ----------------- MAYBE_SEND_LOCATIONS -------------- ",
); );
if let Ok(rows) = context.sql.query_map( if let Ok(ref rows) = context
"SELECT id, locations_send_begin, locations_last_sent \ .sql
.query_map(
"SELECT id, locations_send_begin, locations_last_sent \
FROM chats \ FROM chats \
WHERE locations_send_until>?;", WHERE locations_send_until>?;",
params![now], params![now],
|row| { |row| {
let chat_id: ChatId = row.get(0)?; let chat_id: ChatId = row.get(0)?;
let locations_send_begin: i64 = row.get(1)?; let locations_send_begin: i64 = row.get(1)?;
let locations_last_sent: i64 = row.get(2)?; let locations_last_sent: i64 = row.get(2)?;
continue_streaming = true; continue_streaming = true;
// be a bit tolerant as the timer may not align exactly with time(NULL) // be a bit tolerant as the timer may not align exactly with time(NULL)
if now - locations_last_sent < (60 - 3) { if now - locations_last_sent < (60 - 3) {
Ok(None) Ok(None)
} else { } else {
Ok(Some((chat_id, locations_send_begin, locations_last_sent))) Ok(Some((chat_id, locations_send_begin, locations_last_sent)))
} }
}, },
|rows| { |rows| {
rows.filter_map(|v| v.transpose()) rows.filter_map(|v| v.transpose())
.collect::<Result<Vec<_>, _>>() .collect::<Result<Vec<_>, _>>()
.map_err(Into::into) .map_err(Into::into)
}, },
) { )
.await
{
let msgs = context let msgs = context
.sql .sql
.prepare( .with_conn(|conn| {
"SELECT id \ let mut stmt_locations = conn.prepare_cached(
"SELECT id \
FROM locations \ FROM locations \
WHERE from_id=? \ WHERE from_id=? \
AND timestamp>=? \ AND timestamp>=? \
AND timestamp>? \ AND timestamp>? \
AND independent=0 \ AND independent=0 \
ORDER BY timestamp;", ORDER BY timestamp;",
|mut stmt_locations, _| { )?;
let msgs = rows
.into_iter() let msgs = rows
.filter_map(|(chat_id, locations_send_begin, locations_last_sent)| { .iter()
if !stmt_locations .filter_map(|(chat_id, locations_send_begin, locations_last_sent)| {
.exists(params![ if !stmt_locations
DC_CONTACT_ID_SELF, .exists(params![
locations_send_begin, DC_CONTACT_ID_SELF,
locations_last_sent, locations_send_begin,
]) locations_last_sent,
.unwrap_or_default() ])
{ .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 // if there is no new location, there's nothing to send.
None // however, maybe we want to bypass this test eg. 15 minutes
} else { None
// pending locations are attached automatically to every message, } else {
// so also to this empty text message. // pending locations are attached automatically to every message,
// DC_CMD_LOCATION is only needed to create a nicer subject. // 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. // for optimisation and to avoid flooding the sending queue,
// the easiest way to determine this, is to check for an empty message queue. // we could sending these messages only if we're really online.
// (might not be 100%, however, as positions are sent combined later // the easiest way to determine this, is to check for an empty message queue.
// and dc_set_location() is typically called periodically, this is ok) // (might not be 100%, however, as positions are sent combined later
let mut msg = Message::new(Viewtype::Text); // and dc_set_location() is typically called periodically, this is ok)
msg.hidden = true; let mut msg = Message::new(Viewtype::Text);
msg.param.set_cmd(SystemMessage::LocationOnly); msg.hidden = true;
Some((chat_id, msg)) msg.param.set_cmd(SystemMessage::LocationOnly);
} Some((chat_id, msg))
}) }
.collect::<Vec<_>>(); })
Ok(msgs) .collect::<Vec<_>>();
},
) Ok(msgs)
.unwrap_or_default(); // TODO: Better error handling })
.await
.unwrap_or_default();
for (chat_id, mut msg) in msgs.into_iter() { for (chat_id, mut msg) in msgs.into_iter() {
// TODO: better error handling // TODO: better error handling
chat::send_msg(context, chat_id, &mut msg) chat::send_msg(context, *chat_id, &mut msg)
.await .await
.unwrap_or_default(); .unwrap_or_default();
} }
@@ -642,19 +667,26 @@ pub(crate) async fn job_maybe_send_locations(context: &Context, _job: &Job) -> j
job::Status::Finished(Ok(())) job::Status::Finished(Ok(()))
} }
#[allow(non_snake_case)] pub(crate) async fn job_maybe_send_locations_ended(
pub(crate) fn JobMaybeSendLocationsEnded(context: &Context, job: &mut Job) -> job::Status { context: &Context,
job: &mut Job,
) -> job::Status {
// this function is called when location-streaming _might_ have ended for a chat. // this function is called when location-streaming _might_ have ended for a chat.
// the function checks, if location-streaming is really ended; // the function checks, if location-streaming is really ended;
// if so, a device-message is added if not yet done. // if so, a device-message is added if not yet done.
let chat_id = ChatId::new(job.foreign_id); let chat_id = ChatId::new(job.foreign_id);
let (send_begin, send_until) = job_try!(context.sql.query_row( let (send_begin, send_until) = job_try!(
"SELECT locations_send_begin, locations_send_until FROM chats WHERE id=?", context
params![chat_id], .sql
|row| Ok((row.get::<_, i64>(0)?, row.get::<_, i64>(1)?)), .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) { if !(send_begin != 0 && time() <= send_until) {
// still streaming - // still streaming -
@@ -665,7 +697,7 @@ pub(crate) fn JobMaybeSendLocationsEnded(context: &Context, job: &mut Job) -> jo
job_try!(context.sql.execute( job_try!(context.sql.execute(
"UPDATE chats SET locations_send_begin=0, locations_send_until=0 WHERE id=?", "UPDATE chats SET locations_send_begin=0, locations_send_until=0 WHERE id=?",
params![chat_id], params![chat_id],
)); ).await);
let stock_str = context.stock_system_msg(StockMessage::MsgLocationDisabled, "", "", 0); let stock_str = context.stock_system_msg(StockMessage::MsgLocationDisabled, "", "", 0);
chat::add_info_msg(context, chat_id, stock_str); chat::add_info_msg(context, chat_id, stock_str);

View File

@@ -50,59 +50,69 @@ impl LoginParam {
} }
/// Read the login parameters from the database. /// Read the login parameters from the database.
pub fn from_database(context: &Context, prefix: impl AsRef<str>) -> Self { pub async fn from_database(context: &Context, prefix: impl AsRef<str>) -> Self {
let prefix = prefix.as_ref(); let prefix = prefix.as_ref();
let sql = &context.sql; let sql = &context.sql;
let key = format!("{}addr", prefix); let key = format!("{}addr", prefix);
let addr = sql let addr = sql
.get_raw_config(context, key) .get_raw_config(context, key)
.await
.unwrap_or_default() .unwrap_or_default()
.trim() .trim()
.to_string(); .to_string();
let key = format!("{}mail_server", prefix); 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 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 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 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 key = format!("{}imap_certificate_checks", prefix);
let imap_certificate_checks = 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() num_traits::FromPrimitive::from_i32(certificate_checks).unwrap()
} else { } else {
Default::default() Default::default()
}; };
let key = format!("{}send_server", prefix); 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 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 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 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 key = format!("{}smtp_certificate_checks", prefix);
let smtp_certificate_checks = 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() num_traits::FromPrimitive::from_i32(certificate_checks).unwrap()
} else { } else {
Default::default() Default::default()
}; };
let key = format!("{}server_flags", prefix); 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 { LoginParam {
addr, addr,
@@ -125,7 +135,7 @@ impl LoginParam {
} }
/// Save this loginparam to the database. /// Save this loginparam to the database.
pub fn save_to_database( pub async fn save_to_database(
&self, &self,
context: &Context, context: &Context,
prefix: impl AsRef<str>, prefix: impl AsRef<str>,
@@ -134,40 +144,49 @@ impl LoginParam {
let sql = &context.sql; let sql = &context.sql;
let key = format!("{}addr", prefix); 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); 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); 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); 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); 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); 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); 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); 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); 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); 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); 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); 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(()) Ok(())
} }

View File

@@ -19,7 +19,6 @@ use crate::lot::{Lot, LotState, Meaning};
use crate::mimeparser::SystemMessage; use crate::mimeparser::SystemMessage;
use crate::param::*; use crate::param::*;
use crate::pgp::*; use crate::pgp::*;
use crate::sql;
use crate::stock::StockMessage; use crate::stock::StockMessage;
lazy_static! { lazy_static! {
@@ -219,12 +218,12 @@ impl Message {
msg msg
} }
pub fn load_from_db(context: &Context, id: MsgId) -> Result<Message, Error> { pub async fn load_from_db(context: &Context, id: MsgId) -> Result<Message, Error> {
ensure!( ensure!(
!id.is_special(), !id.is_special(),
"Can not load special message IDs from DB." "Can not load special message IDs from DB."
); );
context let msg = context
.sql .sql
.query_row( .query_row(
concat!( concat!(
@@ -302,25 +301,23 @@ impl Message {
Ok(msg) Ok(msg)
}, },
) )
.map_err(Into::into) .await?;
Ok(msg)
} }
pub fn delete_from_db(context: &Context, msg_id: MsgId) { pub async fn delete_from_db(context: &Context, msg_id: MsgId) {
if let Ok(msg) = Message::load_from_db(context, msg_id) { if let Ok(msg) = Message::load_from_db(context, msg_id).await {
sql::execute( context
context, .sql
&context.sql, .execute("DELETE FROM msgs WHERE id=?;", params![msg.id])
"DELETE FROM msgs WHERE id=?;", .await
params![msg.id], .ok();
) context
.ok(); .sql
sql::execute( .execute("DELETE FROM msgs_mdns WHERE msg_id=?;", params![msg.id])
context, .await
&context.sql, .ok();
"DELETE FROM msgs_mdns WHERE msg_id=?;",
params![msg.id],
)
.ok();
} }
} }
@@ -477,13 +474,13 @@ impl Message {
self.param.get_int(Param::GuaranteeE2ee).unwrap_or_default() != 0 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 mut ret = Lot::new();
let chat_loaded: Chat; let chat_loaded: Chat;
let chat = if let Some(chat) = chat { let chat = if let Some(chat) = 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 = chat;
&chat_loaded &chat_loaded
} else { } else {
@@ -493,7 +490,7 @@ impl Message {
let contact = if self.from_id != DC_CONTACT_ID_SELF as u32 let contact = if self.from_id != DC_CONTACT_ID_SELF as u32
&& (chat.typ == Chattype::Group || chat.typ == Chattype::VerifiedGroup) && (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 { } else {
None None
}; };
@@ -615,14 +612,15 @@ impl Message {
self.save_param_to_disk(context); self.save_param_to_disk(context);
} }
pub fn save_param_to_disk(&mut self, context: &Context) -> bool { pub async fn save_param_to_disk(&mut self, context: &Context) -> bool {
sql::execute( context
context, .sql
&context.sql, .execute(
"UPDATE msgs SET param=? WHERE id=?;", "UPDATE msgs SET param=? WHERE id=?;",
params![self.param.to_string(), self.id], params![self.param.to_string(), self.id],
) )
.is_ok() .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 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() { if msg.is_err() {
return ret; return ret;
} }
let msg = msg.unwrap_or_default(); let msg = msg.unwrap_or_default();
let rawtxt: Option<String> = context.sql.query_get_value( let rawtxt: Option<String> = context
context, .sql
"SELECT txt_raw FROM msgs WHERE id=?;", .query_get_value(
params![msg_id], context,
); "SELECT txt_raw FROM msgs WHERE id=?;",
params![msg_id],
)
.await;
if rawtxt.is_none() { if rawtxt.is_none() {
ret += &format!("Cannot load message {}.", msg_id); 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); ret += &format!("Sent: {}", fts);
let name = Contact::load_from_db(context, msg.from_id) let name = Contact::load_from_db(context, msg.from_id)
.await
.map(|contact| contact.get_name_n_addr()) .map(|contact| contact.get_name_n_addr())
.unwrap_or_default(); .unwrap_or_default();
@@ -952,7 +954,7 @@ pub fn get_mime_headers(context: &Context, msg_id: MsgId) -> Option<String> {
pub async fn delete_msgs(context: &Context, msg_ids: &[MsgId]) { pub async fn delete_msgs(context: &Context, msg_ids: &[MsgId]) {
for msg_id in msg_ids.iter() { for msg_id in msg_ids.iter() {
if let Ok(msg) = Message::load_from_db(context, *msg_id) { if let Ok(msg) = Message::load_from_db(context, *msg_id).await {
if msg.location_id > 0 { if msg.location_id > 0 {
delete_poi_location(context, msg.location_id); delete_poi_location(context, msg.location_id);
} }
@@ -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 { async fn update_msg_chat_id(context: &Context, msg_id: MsgId, chat_id: ChatId) -> bool {
sql::execute( context
context, .sql
&context.sql, .execute(
"UPDATE msgs SET chat_id=? WHERE id=?;", "UPDATE msgs SET chat_id=? WHERE id=?;",
params![chat_id, msg_id], params![chat_id, msg_id],
) )
.is_ok() .await
.is_ok()
} }
fn delete_poi_location(context: &Context, location_id: u32) -> bool { async fn delete_poi_location(context: &Context, location_id: u32) -> bool {
sql::execute( context
context, .sql
&context.sql, .execute(
"DELETE FROM locations WHERE independent = 1 AND id=?;", "DELETE FROM locations WHERE independent = 1 AND id=?;",
params![location_id as i32], params![location_id as i32],
) )
.is_ok() .await
.is_ok()
} }
pub async fn markseen_msgs(context: &Context, msg_ids: &[MsgId]) -> bool { 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; return false;
} }
let msgs = context.sql.prepare( let msgs = context
concat!( .sql
"SELECT", .with_conn(|conn| {
" m.state AS state,", let mut stmt = conn.prepare_cached(concat!(
" c.blocked AS blocked", "SELECT",
" FROM msgs m LEFT JOIN chats c ON c.id=m.chat_id", " m.state AS state,",
" WHERE m.id=? AND m.chat_id>9" " c.blocked AS blocked",
), " FROM msgs m LEFT JOIN chats c ON c.id=m.chat_id",
|mut stmt, _| { " WHERE m.id=? AND m.chat_id>9"
let mut res = Vec::with_capacity(msg_ids.len()); ))?;
let mut msgs = Vec::with_capacity(msg_ids.len());
for id in msg_ids.iter() { for id in msg_ids.iter() {
let query_res = stmt.query_row(params![*id], |row| { let query_res = stmt.query_row(params![*id], |row| {
Ok(( Ok((
@@ -1024,20 +1030,18 @@ pub async fn markseen_msgs(context: &Context, msg_ids: &[MsgId]) -> bool {
if let Err(rusqlite::Error::QueryReturnedNoRows) = query_res { if let Err(rusqlite::Error::QueryReturnedNoRows) = query_res {
continue; continue;
} }
let (state, blocked) = query_res?; let (state, blocked) = query_res
res.push((id, state, blocked)); .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 mut send_event = false;
let msgs = msgs.unwrap_or_default();
for (id, curr_state, curr_blocked) in msgs.into_iter() { for (id, curr_state, curr_blocked) in msgs.into_iter() {
if curr_blocked == Blocked::Not { if curr_blocked == Blocked::Not {
@@ -1071,14 +1075,15 @@ pub async fn markseen_msgs(context: &Context, msg_ids: &[MsgId]) -> bool {
true true
} }
pub fn update_msg_state(context: &Context, msg_id: MsgId, state: MessageState) -> bool { pub async fn update_msg_state(context: &Context, msg_id: MsgId, state: MessageState) -> bool {
sql::execute( context
context, .sql
&context.sql, .execute(
"UPDATE msgs SET state=? WHERE id=?;", "UPDATE msgs SET state=? WHERE id=?;",
params![state, msg_id], params![state, msg_id],
) )
.is_ok() .await
.is_ok()
} }
pub fn star_msgs(context: &Context, msg_ids: &[MsgId], star: bool) -> bool { 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<impl AsRef<str>>) { pub async fn set_msg_failed(context: &Context, msg_id: MsgId, error: Option<impl AsRef<str>>) {
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 {
if msg.state.can_fail() { if msg.state.can_fail() {
msg.state = MessageState::OutFailed; msg.state = MessageState::OutFailed;
} }
@@ -1199,13 +1204,14 @@ pub fn set_msg_failed(context: &Context, msg_id: MsgId, error: Option<impl AsRef
error!(context, "{}", error.as_ref()); error!(context, "{}", error.as_ref());
} }
if sql::execute( if context
context, .sql
&context.sql, .execute(
"UPDATE msgs SET state=?, param=? WHERE id=?;", "UPDATE msgs SET state=?, param=? WHERE id=?;",
params![msg.state, msg.param.to_string(), msg_id], params![msg.state, msg.param.to_string(), msg_id],
) )
.is_ok() .await
.is_ok()
{ {
context.call_cb(Event::MsgFailed { context.call_cb(Event::MsgFailed {
chat_id: msg.chat_id, chat_id: msg.chat_id,
@@ -1429,21 +1435,22 @@ mod tests {
let d = test::dummy_context(); let d = test::dummy_context();
let ctx = &d.ctx; let ctx = &d.ctx;
let contact = let contact = Contact::create(ctx, "", "dest@example.com")
Contact::create(ctx, "", "dest@example.com").expect("failed to create contact"); .await
.expect("failed to create contact");
let res = ctx let res = ctx
.set_config(Config::ConfiguredAddr, Some("self@example.com")) .set_config(Config::ConfiguredAddr, Some("self@example.com"))
.await; .await;
assert!(res.is_ok()); assert!(res.is_ok());
let chat = chat::create_by_contact_id(ctx, contact).unwrap(); let chat = chat::create_by_contact_id(ctx, contact).await.unwrap();
let mut msg = Message::new(Viewtype::Text); let mut msg = Message::new(Viewtype::Text);
let msg_id = chat::prepare_msg(ctx, chat, &mut msg).unwrap(); let msg_id = chat::prepare_msg(ctx, chat, &mut msg).await.unwrap();
let _msg2 = Message::load_from_db(ctx, msg_id).unwrap(); let _msg2 = Message::load_from_db(ctx, msg_id).await.unwrap();
assert_eq!(_msg2.get_filemime(), None); assert_eq!(_msg2.get_filemime(), None);
} }

View File

@@ -195,10 +195,11 @@ impl<'a, 'b> MimeFactory<'a, 'b> {
}) })
} }
fn peerstates_for_recipients(&self) -> Result<Vec<(Option<Peerstate>, &str)>, Error> { async fn peerstates_for_recipients(&self) -> Result<Vec<(Option<Peerstate<'_>>, &str)>, Error> {
let self_addr = self let self_addr = self
.context .context
.get_config(Config::ConfiguredAddr) .get_config(Config::ConfiguredAddr)
.await
.ok_or_else(|| format_err!("Not configured"))?; .ok_or_else(|| format_err!("Not configured"))?;
Ok(self Ok(self

View File

@@ -80,7 +80,7 @@ impl Default for SystemMessage {
const MIME_AC_SETUP_FILE: &str = "application/autocrypt-setup"; const MIME_AC_SETUP_FILE: &str = "application/autocrypt-setup";
impl MimeMessage { impl MimeMessage {
pub fn from_bytes(context: &Context, body: &[u8]) -> Result<Self> { pub async fn from_bytes(context: &Context, body: &[u8]) -> Result<Self> {
let mail = mailparse::parse_mail(body)?; let mail = mailparse::parse_mail(body)?;
let message_time = mail let message_time = mail
@@ -98,7 +98,7 @@ impl MimeMessage {
let mail_raw; let mail_raw;
let mut gossipped_addr = Default::default(); 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)) => { Ok((raw, signatures)) => {
if let Some(raw) = raw { if let Some(raw) = raw {
// Valid autocrypt message, encrypted // Valid autocrypt message, encrypted
@@ -114,7 +114,8 @@ impl MimeMessage {
let gossip_headers = let gossip_headers =
decrypted_mail.headers.get_all_values("Autocrypt-Gossip")?; decrypted_mail.headers.get_all_values("Autocrypt-Gossip")?;
gossipped_addr = 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 // let known protected headers from the decrypted
// part override the unencrypted top-level // part override the unencrypted top-level
@@ -837,7 +838,7 @@ impl MimeMessage {
let mut param = Params::new(); let mut param = Params::new();
param.set(Param::ServerFolder, server_folder.as_ref()); param.set(Param::ServerFolder, server_folder.as_ref());
param.set_int(Param::ServerUid, server_uid as i32); 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); param.set_int(Param::AlsoMove, 1);
} }
job::add(context, Action::MarkseenMdnOnImap, 0, param, 0).await; 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, context: &Context,
message_time: i64, message_time: i64,
mail: &mailparse::ParsedMail<'_>, mail: &mailparse::ParsedMail<'_>,
@@ -878,15 +879,15 @@ fn update_gossip_peerstates(
let mut peerstate = Peerstate::from_addr(context, &context.sql, &header.addr); let mut peerstate = Peerstate::from_addr(context, &context.sql, &header.addr);
if let Some(ref mut peerstate) = peerstate { if let Some(ref mut peerstate) = peerstate {
peerstate.apply_gossip(header, message_time); peerstate.apply_gossip(header, message_time);
peerstate.save_to_db(&context.sql, false)?; peerstate.save_to_db(&context.sql, false).await?;
} else { } else {
let p = Peerstate::from_gossip(context, header, message_time); 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); peerstate = Some(p);
} }
if let Some(peerstate) = peerstate { if let Some(peerstate) = peerstate {
if peerstate.degrade_event.is_some() { if peerstate.degrade_event.is_some() {
handle_degrade_event(context, &peerstate)?; handle_degrade_event(context, &peerstate).await?;
} }
} }

View File

@@ -9,7 +9,7 @@ use crate::aheader::*;
use crate::constants::*; use crate::constants::*;
use crate::context::Context; use crate::context::Context;
use crate::key::{Key, SignedPublicKey}; use crate::key::{Key, SignedPublicKey};
use crate::sql::{self, Sql}; use crate::sql::Sql;
#[derive(Debug)] #[derive(Debug)]
pub enum PeerstateKeyType { 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 { if create {
sql::execute( sql.execute(
self.context,
sql,
"INSERT INTO acpeerstates (addr) VALUES(?);", "INSERT INTO acpeerstates (addr) VALUES(?);",
params![self.addr], params![self.addr],
)?; )
.await?;
} }
if self.to_save == Some(ToSave::All) || create { if self.to_save == Some(ToSave::All) || create {
sql::execute( sql.execute(
self.context,
sql,
"UPDATE acpeerstates \ "UPDATE acpeerstates \
SET last_seen=?, last_seen_autocrypt=?, prefer_encrypted=?, \ SET last_seen=?, last_seen_autocrypt=?, prefer_encrypted=?, \
public_key=?, gossip_timestamp=?, gossip_key=?, public_key_fingerprint=?, gossip_key_fingerprint=?, \ 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.verified_key_fingerprint,
&self.addr, &self.addr,
], ],
)?; ).await?;
} else if self.to_save == Some(ToSave::Timestamps) { } else if self.to_save == Some(ToSave::Timestamps) {
sql::execute( sql.execute(
self.context,
sql,
"UPDATE acpeerstates SET last_seen=?, last_seen_autocrypt=?, gossip_timestamp=? \ "UPDATE acpeerstates SET last_seen=?, last_seen_autocrypt=?, gossip_timestamp=? \
WHERE addr=?;", WHERE addr=?;",
params![ params![
@@ -454,7 +449,8 @@ impl<'a> Peerstate<'a> {
self.gossip_timestamp, self.gossip_timestamp,
&self.addr &self.addr
], ],
)?; )
.await?;
} }
Ok(()) Ok(())

View File

@@ -40,13 +40,13 @@ impl Into<Lot> for Error {
/// Check a scanned QR code. /// Check a scanned QR code.
/// The function should be called after a QR code is scanned. /// 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. /// The function takes the raw text scanned and checks what can be done with it.
pub fn check_qr(context: &Context, qr: impl AsRef<str>) -> Lot { pub async fn check_qr(context: &Context, qr: impl AsRef<str>) -> Lot {
let qr = qr.as_ref(); let qr = qr.as_ref();
info!(context, "Scanned QR code: {}", qr); info!(context, "Scanned QR code: {}", qr);
if qr.starts_with(OPENPGP4FPR_SCHEME) { if qr.starts_with(OPENPGP4FPR_SCHEME) {
decode_openpgp(context, qr) decode_openpgp(context, qr).await
} else if qr.starts_with(DCACCOUNT_SCHEME) { } else if qr.starts_with(DCACCOUNT_SCHEME) {
decode_account(context, qr) decode_account(context, qr)
} else if qr.starts_with(MAILTO_SCHEME) { } else if qr.starts_with(MAILTO_SCHEME) {
@@ -66,7 +66,7 @@ pub fn check_qr(context: &Context, qr: impl AsRef<str>) -> Lot {
/// scheme: `OPENPGP4FPR:FINGERPRINT#a=ADDR&n=NAME&i=INVITENUMBER&s=AUTH` /// scheme: `OPENPGP4FPR:FINGERPRINT#a=ADDR&n=NAME&i=INVITENUMBER&s=AUTH`
/// or: `OPENPGP4FPR:FINGERPRINT#a=ADDR&g=GROUPNAME&x=GROUPID&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 payload = &qr[OPENPGP4FPR_SCHEME.len()..];
let (fingerprint, fragment) = match payload.find('#').map(|offset| { let (fingerprint, fragment) = match payload.find('#').map(|offset| {
@@ -152,10 +152,12 @@ fn decode_openpgp(context: &Context, qr: &str) -> Lot {
peerstate.addr.clone(), peerstate.addr.clone(),
Origin::UnhandledQrScan, Origin::UnhandledQrScan,
) )
.await
.map(|(id, _)| id) .map(|(id, _)| id)
.unwrap_or_default(); .unwrap_or_default();
let (id, _) = chat::create_or_lookup_by_contact_id(context, lot.id, Blocked::Deaddrop) let (id, _) = chat::create_or_lookup_by_contact_id(context, lot.id, Blocked::Deaddrop)
.await
.unwrap_or_default(); .unwrap_or_default();
chat::add_info_msg(context, id, format!("{} verified.", peerstate.addr)); chat::add_info_msg(context, id, format!("{} verified.", peerstate.addr));
@@ -172,6 +174,7 @@ fn decode_openpgp(context: &Context, qr: &str) -> Lot {
lot.state = LotState::QrAskVerifyContact; lot.state = LotState::QrAskVerifyContact;
} }
lot.id = Contact::add_or_lookup(context, &name, &addr, Origin::UnhandledQrScan) lot.id = Contact::add_or_lookup(context, &name, &addr, Origin::UnhandledQrScan)
.await
.map(|(id, _)| id) .map(|(id, _)| id)
.unwrap_or_default(); .unwrap_or_default();

View File

@@ -143,35 +143,40 @@ fn get_self_fingerprint(context: &Context) -> Option<String> {
None 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. /// Take a scanned QR-code and do the setup-contact/join-group handshake.
/// See the ffi-documentation for more details. /// See the ffi-documentation for more details.
pub async fn dc_join_securejoin(context: &Context, qr: &str) -> ChatId { 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 ===== ==== Bob - the joiner's side =====
==== Step 2 in "Setup verified contact" protocol ===== ==== 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; let mut join_vg: bool = false;
info!(context, "Requesting secure-join ...",); info!(context, "Requesting secure-join ...",);
ensure_secret_key_exists(context).ok(); ensure_secret_key_exists(context).await.ok();
if !context.alloc_ongoing() { 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 if qr_scan.state != LotState::QrAskVerifyContact && qr_scan.state != LotState::QrAskVerifyGroup
{ {
error!(context, "Unknown QR code.",); 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, Ok(chat_id) => chat_id,
Err(_) => { Err(_) => {
error!(context, "Unknown contact."); 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() { 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; 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() { while !context.shall_stop_ongoing() {
std::thread::sleep(std::time::Duration::from_millis(200)); std::thread::sleep(std::time::Duration::from_millis(200));
} }
cleanup(&context, contact_chat_id, true, join_vg) cleanup(&context, contact_chat_id, true, join_vg).await
} else { } else {
// for a one-to-one-chat, the chat is already known, return the chat-id, // for a one-to-one-chat, the chat is already known, return the chat-id,
// the verification runs in background // the verification runs in background
@@ -412,7 +417,7 @@ pub(crate) async fn handle_securejoin_handshake(
); );
let contact_chat_id = 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)) => { Ok((chat_id, blocked)) => {
if blocked != Blocked::Not { if blocked != Blocked::Not {
chat_id.unblock(context); chat_id.unblock(context);
@@ -447,7 +452,7 @@ pub(crate) async fn handle_securejoin_handshake(
return Ok(HandshakeMessage::Ignore); 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)."); warn!(context, "Secure-join denied (bad invitenumber).");
return Ok(HandshakeMessage::Ignore); return Ok(HandshakeMessage::Ignore);
} }
@@ -583,7 +588,7 @@ pub(crate) async fn handle_securejoin_handshake(
return Ok(HandshakeMessage::Ignore); 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."); could_not_establish_secure_connection(context, contact_chat_id, "Auth invalid.");
return Ok(HandshakeMessage::Ignore); return Ok(HandshakeMessage::Ignore);
} }
@@ -611,7 +616,7 @@ pub(crate) async fn handle_securejoin_handshake(
return Ok(HandshakeMessage::Ignore); 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, _, _)) => { Ok((group_chat_id, _, _)) => {
if let Err(err) = if let Err(err) =
chat::add_contact_to_chat_ex(context, group_chat_id, contact_id, true) 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 // only after we have returned. It does not impact
// the security invariants of secure-join however. // the security invariants of secure-join however.
let (_, is_verified_group, _) = chat::get_chat_id_by_grpid(context, &group_id) let (_, is_verified_group, _) = chat::get_chat_id_by_grpid(context, &group_id)
.await
.unwrap_or((ChatId::new(0), false, Blocked::Not)); .unwrap_or((ChatId::new(0), false, Blocked::Not));
// when joining a non-verified group // when joining a non-verified group
// the vg-member-added message may be unencrypted // the vg-member-added message may be unencrypted
@@ -711,6 +717,7 @@ pub(crate) async fn handle_securejoin_handshake(
if join_vg if join_vg
&& !context && !context
.is_self_addr(cg_member_added) .is_self_addr(cg_member_added)
.await
.map_err(|_| HandshakeError::NoSelfAddr)? .map_err(|_| HandshakeError::NoSelfAddr)?
{ {
info!(context, "Message belongs to a different handshake (scaled up contact anyway to allow creation of group)."); info!(context, "Message belongs to a different handshake (scaled up contact anyway to allow creation of group).");
@@ -744,7 +751,7 @@ pub(crate) async fn handle_securejoin_handshake(
==== Step 8 in "Out-of-band verified groups" protocol ==== ==== 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 { if contact.is_verified(context) == VerifiedStatus::Unverified {
warn!(context, "vg-member-added-received invalid.",); warn!(context, "vg-member-added-received invalid.",);
return Ok(HandshakeMessage::Ignore); return Ok(HandshakeMessage::Ignore);
@@ -756,6 +763,7 @@ pub(crate) async fn handle_securejoin_handshake(
.map(|s| s.as_str()) .map(|s| s.as_str())
.unwrap_or_else(|| ""); .unwrap_or_else(|| "");
let (group_chat_id, _, _) = chat::get_chat_id_by_grpid(context, &field_grpid) let (group_chat_id, _, _) = chat::get_chat_id_by_grpid(context, &field_grpid)
.await
.map_err(|err| { .map_err(|err| {
warn!(context, "Failed to lookup chat_id from grpid: {}", err); warn!(context, "Failed to lookup chat_id from grpid: {}", err);
HandshakeError::ChatNotFound { 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 // - 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 // - 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 // 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 // with things they cannot fix, so the user is just kicked from the verified group
// (and he will know this and can fix this) // (and he will know this and can fix this)
if Some(DegradeEvent::FingerprintChanged) == peerstate.degrade_event { if Some(DegradeEvent::FingerprintChanged) == peerstate.degrade_event {
let contact_id: i32 = match context.sql.query_get_value( let contact_id: i32 = match context
context, .sql
"SELECT id FROM contacts WHERE addr=?;", .query_get_value(
params![&peerstate.addr], context,
) { "SELECT id FROM contacts WHERE addr=?;",
params![&peerstate.addr],
)
.await
{
None => bail!( None => bail!(
"contact with peerstate.addr {:?} not found", "contact with peerstate.addr {:?} not found",
&peerstate.addr &peerstate.addr
@@ -889,6 +904,7 @@ pub fn handle_degrade_event(context: &Context, peerstate: &Peerstate) -> Result<
if contact_id > 0 { if contact_id > 0 {
let (contact_chat_id, _) = let (contact_chat_id, _) =
chat::create_or_lookup_by_contact_id(context, contact_id as u32, Blocked::Deaddrop) chat::create_or_lookup_by_contact_id(context, contact_id as u32, Blocked::Deaddrop)
.await
.unwrap_or_default(); .unwrap_or_default();
let msg = context let msg = context

1624
src/sql.rs

File diff suppressed because it is too large Load Diff

View File

@@ -544,17 +544,17 @@ mod tests {
async fn test_update_device_chats() { async fn test_update_device_chats() {
let t = dummy_context(); let t = dummy_context();
t.ctx.update_device_chats().ok(); 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); assert_eq!(chats.len(), 2);
chats.get_chat_id(0).delete(&t.ctx).await.ok(); chats.get_chat_id(0).delete(&t.ctx).await.ok();
chats.get_chat_id(1).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); assert_eq!(chats.len(), 0);
// a subsequent call to update_device_chats() must not re-add manally deleted messages or chats // a subsequent call to update_device_chats() must not re-add manally deleted messages or chats
t.ctx.update_device_chats().ok(); t.ctx.update_device_chats().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); assert_eq!(chats.len(), 0);
} }
} }

View File

@@ -83,6 +83,7 @@ pub(crate) async fn configure_alice_keypair(ctx: &Context) -> String {
.await .await
.unwrap(); .unwrap();
key::store_self_keypair(&ctx, &keypair, key::KeyPairUse::Default) key::store_self_keypair(&ctx, &keypair, key::KeyPairUse::Default)
.await
.expect("Failed to save Alice's key"); .expect("Failed to save Alice's key");
keypair.addr.to_string() keypair.addr.to_string()
} }

View File

@@ -9,7 +9,6 @@ use deltachat_derive::*;
use crate::chat::ChatId; use crate::chat::ChatId;
use crate::context::Context; use crate::context::Context;
use crate::dc_tools::*; use crate::dc_tools::*;
use crate::sql;
/// Token namespace /// Token namespace
#[derive(Debug, Display, Clone, Copy, PartialEq, Eq, FromPrimitive, ToPrimitive, ToSql, FromSql)] #[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. /// Creates a new token and saves it into the database.
/// Returns created token. /// 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 // foreign_id may be 0
let token = dc_create_id(); let token = dc_create_id();
sql::execute( context
context, .sql
&context.sql, .execute(
"INSERT INTO tokens (namespc, foreign_id, token, timestamp) VALUES (?, ?, ?, ?);", "INSERT INTO tokens (namespc, foreign_id, token, timestamp) VALUES (?, ?, ?, ?);",
params![namespace, foreign_id, &token, time()], params![namespace, foreign_id, &token, time()],
) )
.ok(); .await
.ok();
token token
} }
pub fn lookup(context: &Context, namespace: Namespace, foreign_id: ChatId) -> Option<String> { pub async fn lookup(context: &Context, namespace: Namespace, foreign_id: ChatId) -> Option<String> {
context.sql.query_get_value::<_, String>( context
context, .sql
"SELECT token FROM tokens WHERE namespc=? AND foreign_id=?;", .query_get_value::<_, String>(
params![namespace, foreign_id], 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 { pub async 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)) 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 context
.sql .sql
.exists( .exists(
"SELECT id FROM tokens WHERE namespc=? AND token=?;", "SELECT id FROM tokens WHERE namespc=? AND token=?;",
params![namespace, token], params![namespace, token],
) )
.await
.unwrap_or_default() .unwrap_or_default()
} }