mirror of
https://github.com/chatmail/core.git
synced 2026-04-28 19:06:35 +03:00
start making sql async
This commit is contained in:
@@ -49,7 +49,7 @@ async fn main() {
|
||||
let ctx1 = ctx.clone();
|
||||
let t1 = async_std::task::spawn(async move {
|
||||
while *r1.read().await {
|
||||
// perform_inbox_jobs(&ctx1).await;
|
||||
perform_inbox_jobs(&ctx1).await;
|
||||
if *r1.read().await {
|
||||
perform_inbox_fetch(&ctx1).await;
|
||||
|
||||
|
||||
1452
src/chat.rs
1452
src/chat.rs
File diff suppressed because it is too large
Load Diff
152
src/chatlist.rs
152
src/chatlist.rs
@@ -85,7 +85,7 @@ impl Chatlist {
|
||||
/// are returned.
|
||||
/// `query_contact_id`: An optional contact ID for filtering the list. Only chats including this contact ID
|
||||
/// are returned.
|
||||
pub fn try_load(
|
||||
pub async fn try_load(
|
||||
context: &Context,
|
||||
listflags: usize,
|
||||
query: Option<&str>,
|
||||
@@ -139,11 +139,13 @@ impl Chatlist {
|
||||
params![MessageState::OutDraft, query_contact_id as i32, ChatVisibility::Pinned],
|
||||
process_row,
|
||||
process_rows,
|
||||
)?
|
||||
).await?
|
||||
} else if 0 != listflags & DC_GCL_ARCHIVED_ONLY {
|
||||
// show archived chats
|
||||
context.sql.query_map(
|
||||
"SELECT c.id, m.id
|
||||
context
|
||||
.sql
|
||||
.query_map(
|
||||
"SELECT c.id, m.id
|
||||
FROM chats c
|
||||
LEFT JOIN msgs m
|
||||
ON c.id=m.chat_id
|
||||
@@ -157,23 +159,26 @@ impl Chatlist {
|
||||
AND c.archived=1
|
||||
GROUP BY c.id
|
||||
ORDER BY IFNULL(m.timestamp,c.created_timestamp) DESC, m.id DESC;",
|
||||
params![MessageState::OutDraft],
|
||||
process_row,
|
||||
process_rows,
|
||||
)?
|
||||
params![MessageState::OutDraft],
|
||||
process_row,
|
||||
process_rows,
|
||||
)
|
||||
.await?
|
||||
} else if let Some(query) = query {
|
||||
let query = query.trim().to_string();
|
||||
ensure!(!query.is_empty(), "missing query");
|
||||
|
||||
// allow searching over special names that may change at any time
|
||||
// when the ui calls set_stock_translation()
|
||||
if let Err(err) = update_special_chat_names(context) {
|
||||
if let Err(err) = update_special_chat_names(context).await {
|
||||
warn!(context, "cannot update special chat names: {:?}", err)
|
||||
}
|
||||
|
||||
let str_like_cmd = format!("%{}%", query);
|
||||
context.sql.query_map(
|
||||
"SELECT c.id, m.id
|
||||
context
|
||||
.sql
|
||||
.query_map(
|
||||
"SELECT c.id, m.id
|
||||
FROM chats c
|
||||
LEFT JOIN msgs m
|
||||
ON c.id=m.chat_id
|
||||
@@ -187,10 +192,11 @@ impl Chatlist {
|
||||
AND c.name LIKE ?
|
||||
GROUP BY c.id
|
||||
ORDER BY IFNULL(m.timestamp,c.created_timestamp) DESC, m.id DESC;",
|
||||
params![MessageState::OutDraft, str_like_cmd],
|
||||
process_row,
|
||||
process_rows,
|
||||
)?
|
||||
params![MessageState::OutDraft, str_like_cmd],
|
||||
process_row,
|
||||
process_rows,
|
||||
)
|
||||
.await?
|
||||
} else {
|
||||
// show normal chatlist
|
||||
let sort_id_up = if 0 != listflags & DC_GCL_FOR_FORWARDING {
|
||||
@@ -218,9 +224,10 @@ impl Chatlist {
|
||||
params![MessageState::OutDraft, ChatVisibility::Archived, sort_id_up, ChatVisibility::Pinned],
|
||||
process_row,
|
||||
process_rows,
|
||||
)?;
|
||||
).await?;
|
||||
if 0 == listflags & DC_GCL_NO_SPECIALS {
|
||||
if let Some(last_deaddrop_fresh_msg_id) = get_last_deaddrop_fresh_msg(context) {
|
||||
if let Some(last_deaddrop_fresh_msg_id) = get_last_deaddrop_fresh_msg(context).await
|
||||
{
|
||||
if 0 == listflags & DC_GCL_FOR_FORWARDING {
|
||||
ids.insert(
|
||||
0,
|
||||
@@ -233,7 +240,7 @@ impl Chatlist {
|
||||
ids
|
||||
};
|
||||
|
||||
if add_archived_link_item && dc_get_archived_cnt(context) > 0 {
|
||||
if add_archived_link_item && dc_get_archived_cnt(context).await > 0 {
|
||||
if ids.is_empty() && 0 != listflags & DC_GCL_ADD_ALLDONE_HINT {
|
||||
ids.push((ChatId::new(DC_CHAT_ID_ALLDONE_HINT), MsgId::new(0)));
|
||||
}
|
||||
@@ -285,7 +292,7 @@ impl Chatlist {
|
||||
/// - dc_lot_t::timestamp: the timestamp of the message. 0 if not applicable.
|
||||
/// - dc_lot_t::state: The state of the message as one of the DC_STATE_* constants (see #dc_msg_get_state()).
|
||||
// 0 if not applicable.
|
||||
pub fn get_summary(&self, context: &Context, index: usize, chat: Option<&Chat>) -> Lot {
|
||||
pub async fn get_summary(&self, context: &Context, index: usize, chat: Option<&Chat>) -> Lot {
|
||||
// The summary is created by the chat, not by the last message.
|
||||
// This is because we may want to display drafts here or stuff as
|
||||
// "is typing".
|
||||
@@ -300,7 +307,7 @@ impl Chatlist {
|
||||
let chat_loaded: Chat;
|
||||
let chat = if let Some(chat) = chat {
|
||||
chat
|
||||
} else if let Ok(chat) = Chat::load_from_db(context, self.ids[index].0) {
|
||||
} else if let Ok(chat) = Chat::load_from_db(context, self.ids[index].0).await {
|
||||
chat_loaded = chat;
|
||||
&chat_loaded
|
||||
} else {
|
||||
@@ -310,11 +317,11 @@ impl Chatlist {
|
||||
let lastmsg_id = self.ids[index].1;
|
||||
let mut lastcontact = None;
|
||||
|
||||
let lastmsg = if let Ok(lastmsg) = Message::load_from_db(context, lastmsg_id) {
|
||||
let lastmsg = if let Ok(lastmsg) = Message::load_from_db(context, lastmsg_id).await {
|
||||
if lastmsg.from_id != DC_CONTACT_ID_SELF
|
||||
&& (chat.typ == Chattype::Group || chat.typ == Chattype::VerifiedGroup)
|
||||
{
|
||||
lastcontact = Contact::load_from_db(context, lastmsg.from_id).ok();
|
||||
lastcontact = Contact::load_from_db(context, lastmsg.from_id).await.ok();
|
||||
}
|
||||
|
||||
Some(lastmsg)
|
||||
@@ -340,7 +347,7 @@ impl Chatlist {
|
||||
}
|
||||
|
||||
/// Returns the number of archived chats
|
||||
pub fn dc_get_archived_cnt(context: &Context) -> u32 {
|
||||
pub async fn dc_get_archived_cnt(context: &Context) -> u32 {
|
||||
context
|
||||
.sql
|
||||
.query_get_value(
|
||||
@@ -348,26 +355,30 @@ pub fn dc_get_archived_cnt(context: &Context) -> u32 {
|
||||
"SELECT COUNT(*) FROM chats WHERE blocked=0 AND archived=1;",
|
||||
params![],
|
||||
)
|
||||
.await
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
fn get_last_deaddrop_fresh_msg(context: &Context) -> Option<MsgId> {
|
||||
async fn get_last_deaddrop_fresh_msg(context: &Context) -> Option<MsgId> {
|
||||
// We have an index over the state-column, this should be
|
||||
// sufficient as there are typically only few fresh messages.
|
||||
context.sql.query_get_value(
|
||||
context,
|
||||
concat!(
|
||||
"SELECT m.id",
|
||||
" FROM msgs m",
|
||||
" LEFT JOIN chats c",
|
||||
" ON c.id=m.chat_id",
|
||||
" WHERE m.state=10",
|
||||
" AND m.hidden=0",
|
||||
" AND c.blocked=2",
|
||||
" ORDER BY m.timestamp DESC, m.id DESC;"
|
||||
),
|
||||
params![],
|
||||
)
|
||||
context
|
||||
.sql
|
||||
.query_get_value(
|
||||
context,
|
||||
concat!(
|
||||
"SELECT m.id",
|
||||
" FROM msgs m",
|
||||
" LEFT JOIN chats c",
|
||||
" ON c.id=m.chat_id",
|
||||
" WHERE m.state=10",
|
||||
" AND m.hidden=0",
|
||||
" AND c.blocked=2",
|
||||
" ORDER BY m.timestamp DESC, m.id DESC;"
|
||||
),
|
||||
params![],
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -376,15 +387,21 @@ mod tests {
|
||||
|
||||
use crate::test_utils::*;
|
||||
|
||||
#[test]
|
||||
fn test_try_load() {
|
||||
#[async_std::test]
|
||||
async fn test_try_load() {
|
||||
let t = dummy_context();
|
||||
let chat_id1 = create_group_chat(&t.ctx, VerifiedStatus::Unverified, "a chat").unwrap();
|
||||
let chat_id2 = create_group_chat(&t.ctx, VerifiedStatus::Unverified, "b chat").unwrap();
|
||||
let chat_id3 = create_group_chat(&t.ctx, VerifiedStatus::Unverified, "c chat").unwrap();
|
||||
let chat_id1 = create_group_chat(&t.ctx, VerifiedStatus::Unverified, "a chat")
|
||||
.await
|
||||
.unwrap();
|
||||
let chat_id2 = create_group_chat(&t.ctx, VerifiedStatus::Unverified, "b chat")
|
||||
.await
|
||||
.unwrap();
|
||||
let chat_id3 = create_group_chat(&t.ctx, VerifiedStatus::Unverified, "c chat")
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// check that the chatlist starts with the most recent message
|
||||
let chats = Chatlist::try_load(&t.ctx, 0, None, None).unwrap();
|
||||
let chats = Chatlist::try_load(&t.ctx, 0, None, None).await.unwrap();
|
||||
assert_eq!(chats.len(), 3);
|
||||
assert_eq!(chats.get_chat_id(0), chat_id3);
|
||||
assert_eq!(chats.get_chat_id(1), chat_id2);
|
||||
@@ -394,20 +411,27 @@ mod tests {
|
||||
let mut msg = Message::new(Viewtype::Text);
|
||||
msg.set_text(Some("hello".to_string()));
|
||||
chat_id2.set_draft(&t.ctx, Some(&mut msg));
|
||||
let chats = Chatlist::try_load(&t.ctx, 0, None, None).unwrap();
|
||||
let chats = Chatlist::try_load(&t.ctx, 0, None, None).await.unwrap();
|
||||
assert_eq!(chats.get_chat_id(0), chat_id2);
|
||||
|
||||
// check chatlist query and archive functionality
|
||||
let chats = Chatlist::try_load(&t.ctx, 0, Some("b"), None).unwrap();
|
||||
let chats = Chatlist::try_load(&t.ctx, 0, Some("b"), None)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(chats.len(), 1);
|
||||
|
||||
let chats = Chatlist::try_load(&t.ctx, DC_GCL_ARCHIVED_ONLY, None, None).unwrap();
|
||||
let chats = Chatlist::try_load(&t.ctx, DC_GCL_ARCHIVED_ONLY, None, None)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(chats.len(), 0);
|
||||
|
||||
chat_id1
|
||||
.set_visibility(&t.ctx, ChatVisibility::Archived)
|
||||
.await
|
||||
.ok();
|
||||
let chats = Chatlist::try_load(&t.ctx, DC_GCL_ARCHIVED_ONLY, None, None).unwrap();
|
||||
let chats = Chatlist::try_load(&t.ctx, DC_GCL_ARCHIVED_ONLY, None, None)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(chats.len(), 1);
|
||||
}
|
||||
|
||||
@@ -427,40 +451,50 @@ mod tests {
|
||||
.is_self_talk());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_search_special_chat_names() {
|
||||
#[async_std::test]
|
||||
async fn test_search_special_chat_names() {
|
||||
let t = dummy_context();
|
||||
t.ctx.update_device_chats().unwrap();
|
||||
|
||||
let chats = Chatlist::try_load(&t.ctx, 0, Some("t-1234-s"), None).unwrap();
|
||||
let chats = Chatlist::try_load(&t.ctx, 0, Some("t-1234-s"), None)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(chats.len(), 0);
|
||||
let chats = Chatlist::try_load(&t.ctx, 0, Some("t-5678-b"), None).unwrap();
|
||||
let chats = Chatlist::try_load(&t.ctx, 0, Some("t-5678-b"), None)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(chats.len(), 0);
|
||||
|
||||
t.ctx
|
||||
.set_stock_translation(StockMessage::SavedMessages, "test-1234-save".to_string())
|
||||
.unwrap();
|
||||
let chats = Chatlist::try_load(&t.ctx, 0, Some("t-1234-s"), None).unwrap();
|
||||
let chats = Chatlist::try_load(&t.ctx, 0, Some("t-1234-s"), None)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(chats.len(), 1);
|
||||
|
||||
t.ctx
|
||||
.set_stock_translation(StockMessage::DeviceMessages, "test-5678-babbel".to_string())
|
||||
.unwrap();
|
||||
let chats = Chatlist::try_load(&t.ctx, 0, Some("t-5678-b"), None).unwrap();
|
||||
let chats = Chatlist::try_load(&t.ctx, 0, Some("t-5678-b"), None)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(chats.len(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_summary_unwrap() {
|
||||
#[async_std::test]
|
||||
async fn test_get_summary_unwrap() {
|
||||
let t = dummy_context();
|
||||
let chat_id1 = create_group_chat(&t.ctx, VerifiedStatus::Unverified, "a chat").unwrap();
|
||||
let chat_id1 = create_group_chat(&t.ctx, VerifiedStatus::Unverified, "a chat")
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let mut msg = Message::new(Viewtype::Text);
|
||||
msg.set_text(Some("foo:\nbar \r\n test".to_string()));
|
||||
chat_id1.set_draft(&t.ctx, Some(&mut msg));
|
||||
|
||||
let chats = Chatlist::try_load(&t.ctx, 0, None, None).unwrap();
|
||||
let summary = chats.get_summary(&t.ctx, 0, None);
|
||||
let chats = Chatlist::try_load(&t.ctx, 0, None, None).await.unwrap();
|
||||
let summary = chats.get_summary(&t.ctx, 0, None).await;
|
||||
assert_eq!(summary.get_text2().unwrap(), "foo: bar test"); // the linebreak should be removed from summary
|
||||
}
|
||||
}
|
||||
|
||||
@@ -95,16 +95,16 @@ pub enum Config {
|
||||
|
||||
impl Context {
|
||||
/// Get a configuration key. Returns `None` if no value is set, and no default value found.
|
||||
pub fn get_config(&self, key: Config) -> Option<String> {
|
||||
pub async fn get_config(&self, key: Config) -> Option<String> {
|
||||
let value = match key {
|
||||
Config::Selfavatar => {
|
||||
let rel_path = self.sql.get_raw_config(self, key);
|
||||
let rel_path = self.sql.get_raw_config(self, key).await;
|
||||
rel_path.map(|p| dc_get_abs_path(self, &p).to_string_lossy().into_owned())
|
||||
}
|
||||
Config::SysVersion => Some((&*DC_VERSION_STR).clone()),
|
||||
Config::SysMsgsizeMaxRecommended => Some(format!("{}", RECOMMENDED_FILE_SIZE)),
|
||||
Config::SysConfigKeys => Some(get_config_keys_string()),
|
||||
_ => self.sql.get_raw_config(self, key),
|
||||
_ => self.sql.get_raw_config(self, key).await,
|
||||
};
|
||||
|
||||
if value.is_some() {
|
||||
@@ -118,14 +118,15 @@ impl Context {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_config_int(&self, key: Config) -> i32 {
|
||||
pub async fn get_config_int(&self, key: Config) -> i32 {
|
||||
self.get_config(key)
|
||||
.await
|
||||
.and_then(|s| s.parse().ok())
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
pub fn get_config_bool(&self, key: Config) -> bool {
|
||||
self.get_config_int(key) != 0
|
||||
pub async fn get_config_bool(&self, key: Config) -> bool {
|
||||
self.get_config_int(key).await != 0
|
||||
}
|
||||
|
||||
/// Set the given config key.
|
||||
@@ -134,30 +135,34 @@ impl Context {
|
||||
match key {
|
||||
Config::Selfavatar => {
|
||||
self.sql
|
||||
.execute("UPDATE contacts SET selfavatar_sent=0;", NO_PARAMS)?;
|
||||
.execute("UPDATE contacts SET selfavatar_sent=0;", NO_PARAMS)
|
||||
.await?;
|
||||
self.sql
|
||||
.set_raw_config_bool(self, "attach_selfavatar", true)?;
|
||||
.set_raw_config_bool(self, "attach_selfavatar", true)
|
||||
.await?;
|
||||
match value {
|
||||
Some(value) => {
|
||||
let blob = BlobObject::new_from_path(&self, value)?;
|
||||
blob.recode_to_avatar_size(self)?;
|
||||
self.sql.set_raw_config(self, key, Some(blob.as_name()))
|
||||
self.sql
|
||||
.set_raw_config(self, key, Some(blob.as_name()))
|
||||
.await
|
||||
}
|
||||
None => self.sql.set_raw_config(self, key, None),
|
||||
None => self.sql.set_raw_config(self, key, None).await,
|
||||
}
|
||||
}
|
||||
Config::InboxWatch => {
|
||||
let ret = self.sql.set_raw_config(self, key, value);
|
||||
let ret = self.sql.set_raw_config(self, key, value).await;
|
||||
interrupt_inbox_idle(self).await;
|
||||
ret
|
||||
}
|
||||
Config::SentboxWatch => {
|
||||
let ret = self.sql.set_raw_config(self, key, value);
|
||||
let ret = self.sql.set_raw_config(self, key, value).await;
|
||||
interrupt_sentbox_idle(self).await;
|
||||
ret
|
||||
}
|
||||
Config::MvboxWatch => {
|
||||
let ret = self.sql.set_raw_config(self, key, value);
|
||||
let ret = self.sql.set_raw_config(self, key, value).await;
|
||||
interrupt_mvbox_idle(self).await;
|
||||
ret
|
||||
}
|
||||
@@ -169,9 +174,9 @@ impl Context {
|
||||
value
|
||||
};
|
||||
|
||||
self.sql.set_raw_config(self, key, val)
|
||||
self.sql.set_raw_config(self, key, val).await
|
||||
}
|
||||
_ => self.sql.set_raw_config(self, key, value),
|
||||
_ => self.sql.set_raw_config(self, key, value).await,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -234,7 +239,7 @@ mod tests {
|
||||
.unwrap();
|
||||
assert!(avatar_blob.exists());
|
||||
assert!(std::fs::metadata(&avatar_blob).unwrap().len() < avatar_bytes.len() as u64);
|
||||
let avatar_cfg = t.ctx.get_config(Config::Selfavatar);
|
||||
let avatar_cfg = t.ctx.get_config(Config::Selfavatar).await;
|
||||
assert_eq!(avatar_cfg, avatar_blob.to_str().map(|s| s.to_string()));
|
||||
|
||||
let img = image::open(avatar_src).unwrap();
|
||||
@@ -264,7 +269,7 @@ mod tests {
|
||||
.set_config(Config::Selfavatar, Some(&avatar_src.to_str().unwrap()))
|
||||
.await
|
||||
.unwrap();
|
||||
let avatar_cfg = t.ctx.get_config(Config::Selfavatar);
|
||||
let avatar_cfg = t.ctx.get_config(Config::Selfavatar).await;
|
||||
assert_eq!(avatar_cfg, avatar_src.to_str().map(|s| s.to_string()));
|
||||
|
||||
let img = image::open(avatar_src).unwrap();
|
||||
|
||||
@@ -42,8 +42,8 @@ impl Context {
|
||||
}
|
||||
|
||||
/// Checks if the context is already configured.
|
||||
pub fn is_configured(&self) -> bool {
|
||||
self.sql.get_raw_config_bool(self, "configured")
|
||||
pub async fn is_configured(&self) -> bool {
|
||||
self.sql.get_raw_config_bool(self, "configured").await
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,7 +52,7 @@ impl Context {
|
||||
******************************************************************************/
|
||||
#[allow(clippy::cognitive_complexity)]
|
||||
pub(crate) async fn job_configure_imap(context: &Context) -> job::Status {
|
||||
if !context.sql.is_open() {
|
||||
if !context.sql.is_open().await {
|
||||
error!(context, "Cannot configure, database not opened.",);
|
||||
progress!(context, 0);
|
||||
return job::Status::Finished(Err(format_err!("Database not opened")));
|
||||
@@ -75,7 +75,7 @@ pub(crate) async fn job_configure_imap(context: &Context) -> job::Status {
|
||||
info!(context, "Configure ...",);
|
||||
|
||||
// Variables that are shared between steps:
|
||||
let mut param = LoginParam::from_database(context, "");
|
||||
let mut param = LoginParam::from_database(context, "").await;
|
||||
// need all vars here to be mutable because rust thinks the same step could be called multiple times
|
||||
// and also initialize, because otherwise rust thinks it's used while unitilized, even if thats not the case as the loop goes only forward
|
||||
let mut param_domain = "undefined.undefined".to_owned();
|
||||
@@ -115,6 +115,7 @@ pub(crate) async fn job_configure_imap(context: &Context) -> job::Status {
|
||||
context
|
||||
.sql
|
||||
.set_raw_config(context, "addr", Some(param.addr.as_str()))
|
||||
.await
|
||||
.ok();
|
||||
}
|
||||
progress!(context, 20);
|
||||
@@ -352,8 +353,8 @@ pub(crate) async fn job_configure_imap(context: &Context) -> job::Status {
|
||||
}
|
||||
16 => {
|
||||
progress!(context, 900);
|
||||
let create_mvbox = context.get_config_bool(Config::MvboxWatch)
|
||||
|| context.get_config_bool(Config::MvboxMove);
|
||||
let create_mvbox = context.get_config_bool(Config::MvboxWatch).await
|
||||
|| context.get_config_bool(Config::MvboxMove).await;
|
||||
let imap = &context.inbox_thread.imap;
|
||||
if let Err(err) = imap.ensure_configured_folders(context, create_mvbox).await {
|
||||
warn!(context, "configuring folders failed: {:?}", err);
|
||||
@@ -376,11 +377,13 @@ pub(crate) async fn job_configure_imap(context: &Context) -> job::Status {
|
||||
context,
|
||||
"configured_", /*the trailing underscore is correct*/
|
||||
)
|
||||
.await
|
||||
.ok();
|
||||
|
||||
context
|
||||
.sql
|
||||
.set_raw_config_bool(context, "configured", true)
|
||||
.await
|
||||
.ok();
|
||||
true
|
||||
}
|
||||
@@ -389,7 +392,7 @@ pub(crate) async fn job_configure_imap(context: &Context) -> job::Status {
|
||||
// we generate the keypair just now - we could also postpone this until the first message is sent, however,
|
||||
// this may result in a unexpected and annoying delay when the user sends his very first message
|
||||
// (~30 seconds on a Moto G4 play) and might looks as if message sending is always that slow.
|
||||
success = e2ee::ensure_secret_key_exists(context).is_ok();
|
||||
success = e2ee::ensure_secret_key_exists(context).await.is_ok();
|
||||
info!(context, "key generation completed");
|
||||
progress!(context, 940);
|
||||
break; // We are done here
|
||||
@@ -416,11 +419,15 @@ pub(crate) async fn job_configure_imap(context: &Context) -> job::Status {
|
||||
// this way, the parameters visible to the ui are always in-sync with the current configuration.
|
||||
if success {
|
||||
LoginParam::from_database(context, "")
|
||||
.await
|
||||
.save_to_database(context, "configured_raw_")
|
||||
.await
|
||||
.ok();
|
||||
} else {
|
||||
LoginParam::from_database(context, "configured_raw_")
|
||||
.await
|
||||
.save_to_database(context, "")
|
||||
.await
|
||||
.ok();
|
||||
}
|
||||
|
||||
@@ -428,7 +435,10 @@ pub(crate) async fn job_configure_imap(context: &Context) -> job::Status {
|
||||
if !provider.after_login_hint.is_empty() {
|
||||
let mut msg = Message::new(Viewtype::Text);
|
||||
msg.text = Some(provider.after_login_hint.to_string());
|
||||
if chat::add_device_msg(context, Some("core-provider-info"), Some(&mut msg)).is_err() {
|
||||
if chat::add_device_msg(context, Some("core-provider-info"), Some(&mut msg))
|
||||
.await
|
||||
.is_err()
|
||||
{
|
||||
warn!(context, "cannot add after_login_hint as core-provider-info");
|
||||
}
|
||||
}
|
||||
|
||||
343
src/contact.rs
343
src/contact.rs
@@ -21,7 +21,6 @@ use crate::message::{MessageState, MsgId};
|
||||
use crate::mimeparser::AvatarAction;
|
||||
use crate::param::*;
|
||||
use crate::peerstate::*;
|
||||
use crate::sql;
|
||||
use crate::stock::StockMessage;
|
||||
|
||||
/// Contacts with at least this origin value are shown in the contact list.
|
||||
@@ -164,29 +163,33 @@ pub enum VerifiedStatus {
|
||||
}
|
||||
|
||||
impl Contact {
|
||||
pub fn load_from_db(context: &Context, contact_id: u32) -> crate::sql::Result<Self> {
|
||||
let mut res = context.sql.query_row(
|
||||
"SELECT c.name, c.addr, c.origin, c.blocked, c.authname, c.param
|
||||
pub async fn load_from_db(context: &Context, contact_id: u32) -> crate::sql::Result<Self> {
|
||||
let mut res = context
|
||||
.sql
|
||||
.query_row(
|
||||
"SELECT c.name, c.addr, c.origin, c.blocked, c.authname, c.param
|
||||
FROM contacts c
|
||||
WHERE c.id=?;",
|
||||
params![contact_id as i32],
|
||||
|row| {
|
||||
let contact = Self {
|
||||
id: contact_id,
|
||||
name: row.get::<_, String>(0)?,
|
||||
authname: row.get::<_, String>(4)?,
|
||||
addr: row.get::<_, String>(1)?,
|
||||
blocked: row.get::<_, Option<i32>>(3)?.unwrap_or_default() != 0,
|
||||
origin: row.get(2)?,
|
||||
param: row.get::<_, String>(5)?.parse().unwrap_or_default(),
|
||||
};
|
||||
Ok(contact)
|
||||
},
|
||||
)?;
|
||||
params![contact_id as i32],
|
||||
|row| {
|
||||
let contact = Self {
|
||||
id: contact_id,
|
||||
name: row.get::<_, String>(0)?,
|
||||
authname: row.get::<_, String>(4)?,
|
||||
addr: row.get::<_, String>(1)?,
|
||||
blocked: row.get::<_, Option<i32>>(3)?.unwrap_or_default() != 0,
|
||||
origin: row.get(2)?,
|
||||
param: row.get::<_, String>(5)?.parse().unwrap_or_default(),
|
||||
};
|
||||
Ok(contact)
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
if contact_id == DC_CONTACT_ID_SELF {
|
||||
res.name = context.stock_str(StockMessage::SelfMsg).to_string();
|
||||
res.addr = context
|
||||
.get_config(Config::ConfiguredAddr)
|
||||
.await
|
||||
.unwrap_or_default();
|
||||
} else if contact_id == DC_CONTACT_ID_DEVICE {
|
||||
res.name = context.stock_str(StockMessage::DeviceMessages).to_string();
|
||||
@@ -201,20 +204,21 @@ impl Contact {
|
||||
}
|
||||
|
||||
/// Check if a contact is blocked.
|
||||
pub fn is_blocked_load(context: &Context, id: u32) -> bool {
|
||||
pub async fn is_blocked_load(context: &Context, id: u32) -> bool {
|
||||
Self::load_from_db(context, id)
|
||||
.await
|
||||
.map(|contact| contact.blocked)
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
/// Block the given contact.
|
||||
pub fn block(context: &Context, id: u32) {
|
||||
set_block_contact(context, id, true);
|
||||
pub async fn block(context: &Context, id: u32) {
|
||||
set_block_contact(context, id, true).await;
|
||||
}
|
||||
|
||||
/// Unblock the given contact.
|
||||
pub fn unblock(context: &Context, id: u32) {
|
||||
set_block_contact(context, id, false);
|
||||
pub async fn unblock(context: &Context, id: u32) {
|
||||
set_block_contact(context, id, false).await;
|
||||
}
|
||||
|
||||
/// Add a single contact as a result of an _explicit_ user action.
|
||||
@@ -226,15 +230,19 @@ impl Contact {
|
||||
/// a bunch of addresses.
|
||||
///
|
||||
/// May result in a `#DC_EVENT_CONTACTS_CHANGED` event.
|
||||
pub fn create(context: &Context, name: impl AsRef<str>, addr: impl AsRef<str>) -> Result<u32> {
|
||||
pub async fn create(
|
||||
context: &Context,
|
||||
name: impl AsRef<str>,
|
||||
addr: impl AsRef<str>,
|
||||
) -> Result<u32> {
|
||||
ensure!(
|
||||
!addr.as_ref().is_empty(),
|
||||
"Cannot create contact with empty address"
|
||||
);
|
||||
|
||||
let (contact_id, sth_modified) =
|
||||
Contact::add_or_lookup(context, name, addr, Origin::ManuallyCreated)?;
|
||||
let blocked = Contact::is_blocked_load(context, contact_id);
|
||||
Contact::add_or_lookup(context, name, addr, Origin::ManuallyCreated).await?;
|
||||
let blocked = Contact::is_blocked_load(context, contact_id).await;
|
||||
context.call_cb(Event::ContactsChanged(
|
||||
if sth_modified == Modifier::Created {
|
||||
Some(contact_id)
|
||||
@@ -253,14 +261,15 @@ impl Contact {
|
||||
/// as *noticed*. See also dc_marknoticed_chat() and dc_markseen_msgs()
|
||||
///
|
||||
/// Calling this function usually results in the event `#DC_EVENT_MSGS_CHANGED`.
|
||||
pub fn mark_noticed(context: &Context, id: u32) {
|
||||
if sql::execute(
|
||||
context,
|
||||
&context.sql,
|
||||
"UPDATE msgs SET state=? WHERE from_id=? AND state=?;",
|
||||
params![MessageState::InNoticed, id as i32, MessageState::InFresh],
|
||||
)
|
||||
.is_ok()
|
||||
pub async fn mark_noticed(context: &Context, id: u32) {
|
||||
if context
|
||||
.sql
|
||||
.execute(
|
||||
"UPDATE msgs SET state=? WHERE from_id=? AND state=?;",
|
||||
params![MessageState::InNoticed, id as i32, MessageState::InFresh],
|
||||
)
|
||||
.await
|
||||
.is_ok()
|
||||
{
|
||||
context.call_cb(Event::MsgsChanged {
|
||||
chat_id: ChatId::new(0),
|
||||
@@ -274,7 +283,7 @@ impl Contact {
|
||||
///
|
||||
/// To validate an e-mail address independently of the contact database
|
||||
/// use `dc_may_be_valid_addr()`.
|
||||
pub fn lookup_id_by_addr(context: &Context, addr: impl AsRef<str>) -> u32 {
|
||||
pub async fn lookup_id_by_addr(context: &Context, addr: impl AsRef<str>) -> u32 {
|
||||
if addr.as_ref().is_empty() {
|
||||
return 0;
|
||||
}
|
||||
@@ -282,6 +291,7 @@ impl Contact {
|
||||
let addr_normalized = addr_normalize(addr.as_ref());
|
||||
let addr_self = context
|
||||
.get_config(Config::ConfiguredAddr)
|
||||
.await
|
||||
.unwrap_or_default();
|
||||
|
||||
if addr_cmp(addr_normalized, addr_self) {
|
||||
@@ -296,7 +306,7 @@ impl Contact {
|
||||
DC_CONTACT_ID_LAST_SPECIAL as i32,
|
||||
DC_ORIGIN_MIN_CONTACT_LIST,
|
||||
],
|
||||
).unwrap_or_default()
|
||||
).await.unwrap_or_default()
|
||||
}
|
||||
|
||||
/// Lookup a contact and create it if it does not exist yet.
|
||||
@@ -324,7 +334,7 @@ impl Contact {
|
||||
/// Depending on the origin, both, "row_name" and "row_authname" are updated from "name".
|
||||
///
|
||||
/// Returns the contact_id and a `Modifier` value indicating if a modification occured.
|
||||
pub(crate) fn add_or_lookup(
|
||||
pub(crate) async fn add_or_lookup(
|
||||
context: &Context,
|
||||
name: impl AsRef<str>,
|
||||
addr: impl AsRef<str>,
|
||||
@@ -341,6 +351,7 @@ impl Contact {
|
||||
let addr = addr_normalize(addr.as_ref());
|
||||
let addr_self = context
|
||||
.get_config(Config::ConfiguredAddr)
|
||||
.await
|
||||
.unwrap_or_default();
|
||||
|
||||
if addr_cmp(addr, addr_self) {
|
||||
@@ -396,7 +407,8 @@ impl Contact {
|
||||
|
||||
Ok((row_id, row_name, row_addr, row_origin, row_authname))
|
||||
},
|
||||
) {
|
||||
)
|
||||
.await {
|
||||
row_id = id;
|
||||
if origin as i32 >= row_origin as i32 && addr != row_addr {
|
||||
update_addr = true;
|
||||
@@ -412,37 +424,36 @@ impl Contact {
|
||||
&row_name
|
||||
};
|
||||
|
||||
sql::execute(
|
||||
context,
|
||||
&context.sql,
|
||||
"UPDATE contacts SET name=?, addr=?, origin=?, authname=? WHERE id=?;",
|
||||
params![
|
||||
new_name,
|
||||
if update_addr { addr } else { &row_addr },
|
||||
if origin > row_origin {
|
||||
origin
|
||||
} else {
|
||||
row_origin
|
||||
},
|
||||
if update_authname {
|
||||
name.as_ref()
|
||||
} else {
|
||||
&row_authname
|
||||
},
|
||||
row_id
|
||||
],
|
||||
)
|
||||
.ok();
|
||||
context
|
||||
.sql
|
||||
.execute(
|
||||
"UPDATE contacts SET name=?, addr=?, origin=?, authname=? WHERE id=?;",
|
||||
params![
|
||||
new_name,
|
||||
if update_addr { addr } else { &row_addr },
|
||||
if origin > row_origin {
|
||||
origin
|
||||
} else {
|
||||
row_origin
|
||||
},
|
||||
if update_authname {
|
||||
name.as_ref()
|
||||
} else {
|
||||
&row_authname
|
||||
},
|
||||
row_id
|
||||
],
|
||||
)
|
||||
.await
|
||||
.ok();
|
||||
|
||||
if update_name {
|
||||
// Update the contact name also if it is used as a group name.
|
||||
// This is one of the few duplicated data, however, getting the chat list is easier this way.
|
||||
sql::execute(
|
||||
context,
|
||||
&context.sql,
|
||||
context.sql.execute(
|
||||
"UPDATE chats SET name=? WHERE type=? AND id IN(SELECT chat_id FROM chats_contacts WHERE contact_id=?);",
|
||||
params![new_name, Chattype::Single, row_id]
|
||||
).ok();
|
||||
).await.ok();
|
||||
}
|
||||
sth_modified = Modifier::Modified;
|
||||
}
|
||||
@@ -451,20 +462,24 @@ impl Contact {
|
||||
update_authname = true;
|
||||
}
|
||||
|
||||
if sql::execute(
|
||||
context,
|
||||
&context.sql,
|
||||
"INSERT INTO contacts (name, addr, origin, authname) VALUES(?, ?, ?, ?);",
|
||||
params![
|
||||
name.as_ref(),
|
||||
addr,
|
||||
origin,
|
||||
if update_authname { name.as_ref() } else { "" }
|
||||
],
|
||||
)
|
||||
.is_ok()
|
||||
if context
|
||||
.sql
|
||||
.execute(
|
||||
"INSERT INTO contacts (name, addr, origin, authname) VALUES(?, ?, ?, ?);",
|
||||
params![
|
||||
name.as_ref(),
|
||||
addr,
|
||||
origin,
|
||||
if update_authname { name.as_ref() } else { "" }
|
||||
],
|
||||
)
|
||||
.await
|
||||
.is_ok()
|
||||
{
|
||||
row_id = sql::get_rowid(context, &context.sql, "contacts", "addr", addr);
|
||||
row_id = context
|
||||
.sql
|
||||
.get_rowid(context, "contacts", "addr", addr)
|
||||
.await?;
|
||||
sth_modified = Modifier::Created;
|
||||
info!(context, "added contact id={} addr={}", row_id, addr);
|
||||
} else {
|
||||
@@ -492,12 +507,12 @@ impl Contact {
|
||||
/// The `addr_book` is a multiline string in the format `Name one\nAddress one\nName two\nAddress two`.
|
||||
///
|
||||
/// Returns the number of modified contacts.
|
||||
pub fn add_address_book(context: &Context, addr_book: impl AsRef<str>) -> Result<usize> {
|
||||
pub async fn add_address_book(context: &Context, addr_book: impl AsRef<str>) -> Result<usize> {
|
||||
let mut modify_cnt = 0;
|
||||
|
||||
for (name, addr) in split_address_book(addr_book.as_ref()).into_iter() {
|
||||
let name = normalize_name(name);
|
||||
match Contact::add_or_lookup(context, name, addr, Origin::AddressBook) {
|
||||
match Contact::add_or_lookup(context, name, addr, Origin::AddressBook).await {
|
||||
Err(err) => {
|
||||
warn!(
|
||||
context,
|
||||
@@ -527,13 +542,14 @@ impl Contact {
|
||||
/// - if the flag DC_GCL_VERIFIED_ONLY is set, only verified contacts are returned.
|
||||
/// if DC_GCL_VERIFIED_ONLY is not set, verified and unverified contacts are returned.
|
||||
/// `query` is a string to filter the list.
|
||||
pub fn get_all(
|
||||
pub async fn get_all(
|
||||
context: &Context,
|
||||
listflags: u32,
|
||||
query: Option<impl AsRef<str>>,
|
||||
) -> Result<Vec<u32>> {
|
||||
let self_addr = context
|
||||
.get_config(Config::ConfiguredAddr)
|
||||
.await
|
||||
.unwrap_or_default();
|
||||
|
||||
let mut add_self = false;
|
||||
@@ -549,8 +565,10 @@ impl Contact {
|
||||
.map(|s| s.as_ref().to_string())
|
||||
.unwrap_or_default()
|
||||
);
|
||||
context.sql.query_map(
|
||||
"SELECT c.id FROM contacts c \
|
||||
context
|
||||
.sql
|
||||
.query_map(
|
||||
"SELECT c.id FROM contacts c \
|
||||
LEFT JOIN acpeerstates ps ON c.addr=ps.addr \
|
||||
WHERE c.addr!=?1 \
|
||||
AND c.id>?2 \
|
||||
@@ -559,24 +577,28 @@ impl Contact {
|
||||
AND (c.name LIKE ?4 OR c.addr LIKE ?5) \
|
||||
AND (1=?6 OR LENGTH(ps.verified_key_fingerprint)!=0) \
|
||||
ORDER BY LOWER(c.name||c.addr),c.id;",
|
||||
params![
|
||||
self_addr,
|
||||
DC_CONTACT_ID_LAST_SPECIAL as i32,
|
||||
Origin::IncomingReplyTo,
|
||||
&s3str_like_cmd,
|
||||
&s3str_like_cmd,
|
||||
if flag_verified_only { 0 } else { 1 },
|
||||
],
|
||||
|row| row.get::<_, i32>(0),
|
||||
|ids| {
|
||||
for id in ids {
|
||||
ret.push(id? as u32);
|
||||
}
|
||||
Ok(())
|
||||
},
|
||||
)?;
|
||||
params![
|
||||
self_addr,
|
||||
DC_CONTACT_ID_LAST_SPECIAL as i32,
|
||||
Origin::IncomingReplyTo,
|
||||
&s3str_like_cmd,
|
||||
&s3str_like_cmd,
|
||||
if flag_verified_only { 0 } else { 1 },
|
||||
],
|
||||
|row| row.get::<_, i32>(0),
|
||||
|ids| {
|
||||
for id in ids {
|
||||
ret.push(id? as u32);
|
||||
}
|
||||
Ok(())
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
|
||||
let self_name = context.get_config(Config::Displayname).unwrap_or_default();
|
||||
let self_name = context
|
||||
.get_config(Config::Displayname)
|
||||
.await
|
||||
.unwrap_or_default();
|
||||
let self_name2 = context.stock_str(StockMessage::SelfMsg);
|
||||
|
||||
if let Some(query) = query {
|
||||
@@ -602,7 +624,7 @@ impl Contact {
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
)?;
|
||||
).await?;
|
||||
}
|
||||
|
||||
if flag_add_self && add_self {
|
||||
@@ -612,7 +634,7 @@ impl Contact {
|
||||
Ok(ret)
|
||||
}
|
||||
|
||||
pub fn get_blocked_cnt(context: &Context) -> usize {
|
||||
pub async fn get_blocked_cnt(context: &Context) -> usize {
|
||||
context
|
||||
.sql
|
||||
.query_get_value::<_, isize>(
|
||||
@@ -620,11 +642,12 @@ impl Contact {
|
||||
"SELECT COUNT(*) FROM contacts WHERE id>? AND blocked!=0",
|
||||
params![DC_CONTACT_ID_LAST_SPECIAL as i32],
|
||||
)
|
||||
.await
|
||||
.unwrap_or_default() as usize
|
||||
}
|
||||
|
||||
/// Get blocked contacts.
|
||||
pub fn get_all_blocked(context: &Context) -> Vec<u32> {
|
||||
pub async fn get_all_blocked(context: &Context) -> Vec<u32> {
|
||||
context
|
||||
.sql
|
||||
.query_map(
|
||||
@@ -636,6 +659,7 @@ impl Contact {
|
||||
.map_err(Into::into)
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
@@ -644,14 +668,14 @@ impl Contact {
|
||||
/// This function returns a string explaining the encryption state
|
||||
/// of the contact and if the connection is encrypted the
|
||||
/// fingerprints of the keys involved.
|
||||
pub fn get_encrinfo(context: &Context, contact_id: u32) -> Result<String> {
|
||||
pub async fn get_encrinfo(context: &Context, contact_id: u32) -> Result<String> {
|
||||
let mut ret = String::new();
|
||||
|
||||
if let Ok(contact) = Contact::load_from_db(context, contact_id) {
|
||||
if let Ok(contact) = Contact::load_from_db(context, contact_id).await {
|
||||
let peerstate = Peerstate::from_addr(context, &context.sql, &contact.addr);
|
||||
let loginparam = LoginParam::from_database(context, "configured_");
|
||||
let loginparam = LoginParam::from_database(context, "configured_").await;
|
||||
|
||||
let mut self_key = Key::from_self_public(context, &loginparam.addr, &context.sql);
|
||||
let mut self_key = Key::from_self_public(context, &loginparam.addr, &context.sql).await;
|
||||
|
||||
if peerstate.is_some()
|
||||
&& peerstate
|
||||
@@ -668,8 +692,8 @@ impl Contact {
|
||||
});
|
||||
ret += &p;
|
||||
if self_key.is_none() {
|
||||
e2ee::ensure_secret_key_exists(context)?;
|
||||
self_key = Key::from_self_public(context, &loginparam.addr, &context.sql);
|
||||
e2ee::ensure_secret_key_exists(context).await?;
|
||||
self_key = Key::from_self_public(context, &loginparam.addr, &context.sql).await;
|
||||
}
|
||||
let p = context.stock_str(StockMessage::FingerPrints);
|
||||
ret += &format!(" {}:", p);
|
||||
@@ -718,7 +742,7 @@ impl Contact {
|
||||
/// possible as the contact is in use. In this case, the contact can be blocked.
|
||||
///
|
||||
/// May result in a `#DC_EVENT_CONTACTS_CHANGED` event.
|
||||
pub fn delete(context: &Context, contact_id: u32) -> Result<()> {
|
||||
pub async fn delete(context: &Context, contact_id: u32) -> Result<()> {
|
||||
ensure!(
|
||||
contact_id > DC_CONTACT_ID_LAST_SPECIAL,
|
||||
"Can not delete special contact"
|
||||
@@ -731,6 +755,7 @@ impl Contact {
|
||||
"SELECT COUNT(*) FROM chats_contacts WHERE contact_id=?;",
|
||||
params![contact_id as i32],
|
||||
)
|
||||
.await
|
||||
.unwrap_or_default();
|
||||
|
||||
let count_msgs: i32 = if count_contacts > 0 {
|
||||
@@ -741,18 +766,21 @@ impl Contact {
|
||||
"SELECT COUNT(*) FROM msgs WHERE from_id=? OR to_id=?;",
|
||||
params![contact_id as i32, contact_id as i32],
|
||||
)
|
||||
.await
|
||||
.unwrap_or_default()
|
||||
} else {
|
||||
0
|
||||
};
|
||||
|
||||
if count_msgs == 0 {
|
||||
match sql::execute(
|
||||
context,
|
||||
&context.sql,
|
||||
"DELETE FROM contacts WHERE id=?;",
|
||||
params![contact_id as i32],
|
||||
) {
|
||||
match context
|
||||
.sql
|
||||
.execute(
|
||||
"DELETE FROM contacts WHERE id=?;",
|
||||
params![contact_id as i32],
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(_) => {
|
||||
context.call_cb(Event::ContactsChanged(None));
|
||||
return Ok(());
|
||||
@@ -776,17 +804,20 @@ impl Contact {
|
||||
/// For contact DC_CONTACT_ID_SELF (1), the function returns sth.
|
||||
/// like "Me" in the selected language and the email address
|
||||
/// defined by dc_set_config().
|
||||
pub fn get_by_id(context: &Context, contact_id: u32) -> Result<Contact> {
|
||||
Ok(Contact::load_from_db(context, contact_id)?)
|
||||
pub async fn get_by_id(context: &Context, contact_id: u32) -> Result<Contact> {
|
||||
let contact = Contact::load_from_db(context, contact_id).await?;
|
||||
|
||||
Ok(contact)
|
||||
}
|
||||
|
||||
pub fn update_param(&mut self, context: &Context) -> Result<()> {
|
||||
sql::execute(
|
||||
context,
|
||||
&context.sql,
|
||||
"UPDATE contacts SET param=? WHERE id=?",
|
||||
params![self.param.to_string(), self.id as i32],
|
||||
)?;
|
||||
pub async fn update_param(&mut self, context: &Context) -> Result<()> {
|
||||
context
|
||||
.sql
|
||||
.execute(
|
||||
"UPDATE contacts SET param=? WHERE id=?",
|
||||
params![self.param.to_string(), self.id as i32],
|
||||
)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -856,9 +887,9 @@ impl Contact {
|
||||
/// Get the contact's profile image.
|
||||
/// This is the image set by each remote user on their own
|
||||
/// using dc_set_config(context, "selfavatar", image).
|
||||
pub fn get_profile_image(&self, context: &Context) -> Option<PathBuf> {
|
||||
pub async fn get_profile_image(&self, context: &Context) -> Option<PathBuf> {
|
||||
if self.id == DC_CONTACT_ID_SELF {
|
||||
if let Some(p) = context.get_config(Config::Selfavatar) {
|
||||
if let Some(p) = context.get_config(Config::Selfavatar).await {
|
||||
return Some(PathBuf::from(p));
|
||||
}
|
||||
} else if let Some(image_rel) = self.param.get(Param::ProfileImage) {
|
||||
@@ -916,12 +947,16 @@ impl Contact {
|
||||
VerifiedStatus::Unverified
|
||||
}
|
||||
|
||||
pub fn addr_equals_contact(context: &Context, addr: impl AsRef<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() {
|
||||
return false;
|
||||
}
|
||||
|
||||
if let Ok(contact) = Contact::load_from_db(context, contact_id) {
|
||||
if let Ok(contact) = Contact::load_from_db(context, contact_id).await {
|
||||
if !contact.addr.is_empty() {
|
||||
let normalized_addr = addr_normalize(addr.as_ref());
|
||||
if contact.addr == normalized_addr {
|
||||
@@ -933,8 +968,8 @@ impl Contact {
|
||||
false
|
||||
}
|
||||
|
||||
pub fn get_real_cnt(context: &Context) -> usize {
|
||||
if !context.sql.is_open() {
|
||||
pub async fn get_real_cnt(context: &Context) -> usize {
|
||||
if !context.sql.is_open().await {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -945,11 +980,12 @@ impl Contact {
|
||||
"SELECT COUNT(*) FROM contacts WHERE id>?;",
|
||||
params![DC_CONTACT_ID_LAST_SPECIAL as i32],
|
||||
)
|
||||
.await
|
||||
.unwrap_or_default() as usize
|
||||
}
|
||||
|
||||
pub fn real_exists_by_id(context: &Context, contact_id: u32) -> bool {
|
||||
if !context.sql.is_open() || contact_id <= DC_CONTACT_ID_LAST_SPECIAL {
|
||||
pub async fn real_exists_by_id(context: &Context, contact_id: u32) -> bool {
|
||||
if !context.sql.is_open().await || contact_id <= DC_CONTACT_ID_LAST_SPECIAL {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -959,16 +995,18 @@ impl Contact {
|
||||
"SELECT id FROM contacts WHERE id=?;",
|
||||
params![contact_id as i32],
|
||||
)
|
||||
.await
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
pub fn scaleup_origin_by_id(context: &Context, contact_id: u32, origin: Origin) -> bool {
|
||||
pub async fn scaleup_origin_by_id(context: &Context, contact_id: u32, origin: Origin) -> bool {
|
||||
context
|
||||
.sql
|
||||
.execute(
|
||||
"UPDATE contacts SET origin=? WHERE id=? AND origin<?;",
|
||||
params![origin, contact_id as i32, origin],
|
||||
)
|
||||
.await
|
||||
.is_ok()
|
||||
}
|
||||
}
|
||||
@@ -995,32 +1033,31 @@ pub fn addr_normalize(addr: &str) -> &str {
|
||||
norm
|
||||
}
|
||||
|
||||
fn set_block_contact(context: &Context, contact_id: u32, new_blocking: bool) {
|
||||
async fn set_block_contact(context: &Context, contact_id: u32, new_blocking: bool) {
|
||||
if contact_id <= DC_CONTACT_ID_LAST_SPECIAL {
|
||||
return;
|
||||
}
|
||||
|
||||
if let Ok(contact) = Contact::load_from_db(context, contact_id) {
|
||||
if let Ok(contact) = Contact::load_from_db(context, contact_id).await {
|
||||
if contact.blocked != new_blocking
|
||||
&& sql::execute(
|
||||
context,
|
||||
&context.sql,
|
||||
"UPDATE contacts SET blocked=? WHERE id=?;",
|
||||
params![new_blocking as i32, contact_id as i32],
|
||||
)
|
||||
.is_ok()
|
||||
&& context
|
||||
.sql
|
||||
.execute(
|
||||
"UPDATE contacts SET blocked=? WHERE id=?;",
|
||||
params![new_blocking as i32, contact_id as i32],
|
||||
)
|
||||
.await
|
||||
.is_ok()
|
||||
{
|
||||
// also (un)block all chats with _only_ this contact - we do not delete them to allow a
|
||||
// non-destructive blocking->unblocking.
|
||||
// (Maybe, beside normal chats (type=100) we should also block group chats with only this user.
|
||||
// However, I'm not sure about this point; it may be confusing if the user wants to add other people;
|
||||
// this would result in recreating the same group...)
|
||||
if sql::execute(
|
||||
context,
|
||||
&context.sql,
|
||||
if context.sql.execute(
|
||||
"UPDATE chats SET blocked=? WHERE type=? AND id IN (SELECT chat_id FROM chats_contacts WHERE contact_id=?);",
|
||||
params![new_blocking, 100, contact_id as i32],
|
||||
).is_ok() {
|
||||
).await.is_ok() {
|
||||
Contact::mark_noticed(context, contact_id);
|
||||
context.call_cb(Event::ContactsChanged(None));
|
||||
}
|
||||
@@ -1028,14 +1065,14 @@ fn set_block_contact(context: &Context, contact_id: u32, new_blocking: bool) {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn set_profile_image(
|
||||
pub(crate) async fn set_profile_image(
|
||||
context: &Context,
|
||||
contact_id: u32,
|
||||
profile_image: &AvatarAction,
|
||||
) -> Result<()> {
|
||||
// the given profile image is expected to be already in the blob directory
|
||||
// as profile images can be set only by receiving messages, this should be always the case, however.
|
||||
let mut contact = Contact::load_from_db(context, contact_id)?;
|
||||
let mut contact = Contact::load_from_db(context, contact_id).await?;
|
||||
let changed = match profile_image {
|
||||
AvatarAction::Change(profile_image) => {
|
||||
contact.param.set(Param::ProfileImage, profile_image);
|
||||
@@ -1047,7 +1084,7 @@ pub(crate) fn set_profile_image(
|
||||
}
|
||||
};
|
||||
if changed {
|
||||
contact.update_param(context)?;
|
||||
contact.update_param(context).await?;
|
||||
context.call_cb(Event::ContactsChanged(Some(contact_id)));
|
||||
}
|
||||
Ok(())
|
||||
@@ -1119,8 +1156,8 @@ fn cat_fingerprint(
|
||||
|
||||
impl Context {
|
||||
/// determine whether the specified addr maps to the/a self addr
|
||||
pub fn is_self_addr(&self, addr: &str) -> Result<bool> {
|
||||
let self_addr = match self.get_config(Config::ConfiguredAddr) {
|
||||
pub async fn is_self_addr(&self, addr: &str) -> Result<bool> {
|
||||
let self_addr = match self.get_config(Config::ConfiguredAddr).await {
|
||||
Some(s) => s,
|
||||
None => return Err(Error::NotConfigured),
|
||||
};
|
||||
@@ -1222,11 +1259,11 @@ mod tests {
|
||||
#[async_std::test]
|
||||
async fn test_is_self_addr() -> Result<()> {
|
||||
let t = test_context(None);
|
||||
assert!(t.ctx.is_self_addr("me@me.org").is_err());
|
||||
assert!(t.ctx.is_self_addr("me@me.org").await.is_err());
|
||||
|
||||
let addr = configure_alice_keypair(&t.ctx).await;
|
||||
assert_eq!(t.ctx.is_self_addr("me@me.org")?, false);
|
||||
assert_eq!(t.ctx.is_self_addr(&addr)?, true);
|
||||
assert_eq!(t.ctx.is_self_addr("me@me.org").await?, false);
|
||||
assert_eq!(t.ctx.is_self_addr(&addr).await?, true);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
118
src/context.rs
118
src/context.rs
@@ -3,7 +3,7 @@
|
||||
use std::collections::HashMap;
|
||||
use std::ffi::OsString;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::{Arc, Mutex, RwLock};
|
||||
use std::sync::{atomic::AtomicBool, Arc, Mutex, RwLock};
|
||||
|
||||
use crate::chat::*;
|
||||
use crate::config::Config;
|
||||
@@ -39,8 +39,8 @@ pub struct Context {
|
||||
/// Blob directory path
|
||||
blobdir: PathBuf,
|
||||
pub sql: Sql,
|
||||
pub perform_inbox_jobs_needed: Arc<RwLock<bool>>,
|
||||
pub probe_imap_network: Arc<RwLock<bool>>,
|
||||
pub perform_inbox_jobs_needed: AtomicBool,
|
||||
pub probe_imap_network: AtomicBool,
|
||||
pub inbox_thread: JobThread,
|
||||
pub sentbox_thread: JobThread,
|
||||
pub mvbox_thread: JobThread,
|
||||
@@ -81,7 +81,11 @@ pub fn get_info() -> HashMap<&'static str, String> {
|
||||
|
||||
impl Context {
|
||||
/// Creates new context.
|
||||
pub fn new(cb: Box<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();
|
||||
|
||||
let mut blob_fname = OsString::new();
|
||||
@@ -91,10 +95,10 @@ impl Context {
|
||||
if !blobdir.exists() {
|
||||
std::fs::create_dir_all(&blobdir)?;
|
||||
}
|
||||
Context::with_blobdir(cb, os_name, dbfile, blobdir)
|
||||
Context::with_blobdir(cb, os_name, dbfile, blobdir).await
|
||||
}
|
||||
|
||||
pub fn with_blobdir(
|
||||
pub async fn with_blobdir(
|
||||
cb: Box<ContextCallback>,
|
||||
os_name: String,
|
||||
dbfile: PathBuf,
|
||||
@@ -120,14 +124,14 @@ impl Context {
|
||||
inbox_thread: JobThread::new("INBOX", "configured_inbox_folder", Imap::new()),
|
||||
sentbox_thread: JobThread::new("SENTBOX", "configured_sentbox_folder", Imap::new()),
|
||||
mvbox_thread: JobThread::new("MVBOX", "configured_mvbox_folder", Imap::new()),
|
||||
probe_imap_network: Arc::new(RwLock::new(false)),
|
||||
perform_inbox_jobs_needed: Arc::new(RwLock::new(false)),
|
||||
probe_imap_network: Default::default(),
|
||||
perform_inbox_jobs_needed: Default::default(),
|
||||
generating_key_mutex: Mutex::new(()),
|
||||
translated_stockstrings: RwLock::new(HashMap::new()),
|
||||
};
|
||||
|
||||
ensure!(
|
||||
ctx.sql.open(&ctx, &ctx.dbfile, false),
|
||||
ctx.sql.open(&ctx, &ctx.dbfile, false).await,
|
||||
"Failed opening sqlite database"
|
||||
);
|
||||
|
||||
@@ -208,57 +212,66 @@ impl Context {
|
||||
* UI chat/message related API
|
||||
******************************************************************************/
|
||||
|
||||
pub fn get_info(&self) -> HashMap<&'static str, String> {
|
||||
pub async fn get_info(&self) -> HashMap<&'static str, String> {
|
||||
let unset = "0";
|
||||
let l = LoginParam::from_database(self, "");
|
||||
let l2 = LoginParam::from_database(self, "configured_");
|
||||
let displayname = self.get_config(Config::Displayname);
|
||||
let chats = get_chat_cnt(self) as usize;
|
||||
let l = LoginParam::from_database(self, "").await;
|
||||
let l2 = LoginParam::from_database(self, "configured_").await;
|
||||
let displayname = self.get_config(Config::Displayname).await;
|
||||
let chats = get_chat_cnt(self).await as usize;
|
||||
let real_msgs = message::get_real_msg_cnt(self) as usize;
|
||||
let deaddrop_msgs = message::get_deaddrop_msg_cnt(self) as usize;
|
||||
let contacts = Contact::get_real_cnt(self) as usize;
|
||||
let is_configured = self.get_config_int(Config::Configured);
|
||||
let contacts = Contact::get_real_cnt(self).await as usize;
|
||||
let is_configured = self.get_config_int(Config::Configured).await;
|
||||
let dbversion = self
|
||||
.sql
|
||||
.get_raw_config_int(self, "dbversion")
|
||||
.await
|
||||
.unwrap_or_default();
|
||||
|
||||
let e2ee_enabled = self.get_config_int(Config::E2eeEnabled);
|
||||
let mdns_enabled = self.get_config_int(Config::MdnsEnabled);
|
||||
let bcc_self = self.get_config_int(Config::BccSelf);
|
||||
let e2ee_enabled = self.get_config_int(Config::E2eeEnabled).await;
|
||||
let mdns_enabled = self.get_config_int(Config::MdnsEnabled).await;
|
||||
let bcc_self = self.get_config_int(Config::BccSelf).await;
|
||||
|
||||
let prv_key_cnt: Option<isize> =
|
||||
self.sql
|
||||
.query_get_value(self, "SELECT COUNT(*) FROM keypairs;", rusqlite::NO_PARAMS);
|
||||
let prv_key_cnt: Option<isize> = self
|
||||
.sql
|
||||
.query_get_value(self, "SELECT COUNT(*) FROM keypairs;", rusqlite::NO_PARAMS)
|
||||
.await;
|
||||
|
||||
let pub_key_cnt: Option<isize> = self.sql.query_get_value(
|
||||
self,
|
||||
"SELECT COUNT(*) FROM acpeerstates;",
|
||||
rusqlite::NO_PARAMS,
|
||||
);
|
||||
let pub_key_cnt: Option<isize> = self
|
||||
.sql
|
||||
.query_get_value(
|
||||
self,
|
||||
"SELECT COUNT(*) FROM acpeerstates;",
|
||||
rusqlite::NO_PARAMS,
|
||||
)
|
||||
.await;
|
||||
|
||||
let fingerprint_str = if let Some(key) = Key::from_self_public(self, &l2.addr, &self.sql) {
|
||||
key.fingerprint()
|
||||
} else {
|
||||
"<Not yet calculated>".into()
|
||||
};
|
||||
let fingerprint_str =
|
||||
if let Some(key) = Key::from_self_public(self, &l2.addr, &self.sql).await {
|
||||
key.fingerprint()
|
||||
} else {
|
||||
"<Not yet calculated>".into()
|
||||
};
|
||||
|
||||
let inbox_watch = self.get_config_int(Config::InboxWatch);
|
||||
let sentbox_watch = self.get_config_int(Config::SentboxWatch);
|
||||
let mvbox_watch = self.get_config_int(Config::MvboxWatch);
|
||||
let mvbox_move = self.get_config_int(Config::MvboxMove);
|
||||
let inbox_watch = self.get_config_int(Config::InboxWatch).await;
|
||||
let sentbox_watch = self.get_config_int(Config::SentboxWatch).await;
|
||||
let mvbox_watch = self.get_config_int(Config::MvboxWatch).await;
|
||||
let mvbox_move = self.get_config_int(Config::MvboxMove).await;
|
||||
let folders_configured = self
|
||||
.sql
|
||||
.get_raw_config_int(self, "folders_configured")
|
||||
.await
|
||||
.unwrap_or_default();
|
||||
|
||||
let configured_sentbox_folder = self
|
||||
.sql
|
||||
.get_raw_config(self, "configured_sentbox_folder")
|
||||
.await
|
||||
.unwrap_or_else(|| "<unset>".to_string());
|
||||
let configured_mvbox_folder = self
|
||||
.sql
|
||||
.get_raw_config(self, "configured_mvbox_folder")
|
||||
.await
|
||||
.unwrap_or_else(|| "<unset>".to_string());
|
||||
|
||||
let mut res = get_info();
|
||||
@@ -273,6 +286,7 @@ impl Context {
|
||||
res.insert(
|
||||
"selfavatar",
|
||||
self.get_config(Config::Selfavatar)
|
||||
.await
|
||||
.unwrap_or_else(|| "<unset>".to_string()),
|
||||
);
|
||||
res.insert("is_configured", is_configured.to_string());
|
||||
@@ -301,7 +315,7 @@ impl Context {
|
||||
res
|
||||
}
|
||||
|
||||
pub fn get_fresh_msgs(&self) -> Vec<MsgId> {
|
||||
pub async fn get_fresh_msgs(&self) -> Vec<MsgId> {
|
||||
let show_deaddrop = 0;
|
||||
self.sql
|
||||
.query_map(
|
||||
@@ -329,11 +343,12 @@ impl Context {
|
||||
Ok(ret)
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
pub fn search_msgs(&self, chat_id: ChatId, query: impl AsRef<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();
|
||||
if real_query.is_empty() {
|
||||
return Vec::new();
|
||||
@@ -383,6 +398,7 @@ impl Context {
|
||||
Ok(ret)
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
@@ -390,8 +406,11 @@ impl Context {
|
||||
folder_name.as_ref() == "INBOX"
|
||||
}
|
||||
|
||||
pub fn is_sentbox(&self, folder_name: impl AsRef<str>) -> bool {
|
||||
let sentbox_name = self.sql.get_raw_config(self, "configured_sentbox_folder");
|
||||
pub async fn is_sentbox(&self, folder_name: impl AsRef<str>) -> bool {
|
||||
let sentbox_name = self
|
||||
.sql
|
||||
.get_raw_config(self, "configured_sentbox_folder")
|
||||
.await;
|
||||
if let Some(name) = sentbox_name {
|
||||
name == folder_name.as_ref()
|
||||
} else {
|
||||
@@ -399,8 +418,11 @@ impl Context {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_mvbox(&self, folder_name: impl AsRef<str>) -> bool {
|
||||
let mvbox_name = self.sql.get_raw_config(self, "configured_mvbox_folder");
|
||||
pub async fn is_mvbox(&self, folder_name: impl AsRef<str>) -> bool {
|
||||
let mvbox_name = self
|
||||
.sql
|
||||
.get_raw_config(self, "configured_mvbox_folder")
|
||||
.await;
|
||||
|
||||
if let Some(name) = mvbox_name {
|
||||
name == folder_name.as_ref()
|
||||
@@ -410,14 +432,14 @@ impl Context {
|
||||
}
|
||||
|
||||
pub async fn do_heuristics_moves(&self, folder: &str, msg_id: MsgId) {
|
||||
if !self.get_config_bool(Config::MvboxMove) {
|
||||
if !self.get_config_bool(Config::MvboxMove).await {
|
||||
return;
|
||||
}
|
||||
|
||||
if self.is_mvbox(folder) {
|
||||
if self.is_mvbox(folder).await {
|
||||
return;
|
||||
}
|
||||
if let Ok(msg) = Message::load_from_db(self, msg_id) {
|
||||
if let Ok(msg) = Message::load_from_db(self, msg_id).await {
|
||||
if msg.is_setupmessage() {
|
||||
// do not move setup messages;
|
||||
// there may be a non-delta device that wants to handle it
|
||||
@@ -567,11 +589,11 @@ mod tests {
|
||||
std::mem::drop(t.ctx);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_info() {
|
||||
#[async_std::test]
|
||||
async fn test_get_info() {
|
||||
let t = dummy_context();
|
||||
|
||||
let info = t.ctx.get_info();
|
||||
let info = t.ctx.get_info().await;
|
||||
assert!(info.get("database_dir").is_some());
|
||||
}
|
||||
|
||||
|
||||
@@ -17,7 +17,6 @@ use crate::mimeparser::*;
|
||||
use crate::param::*;
|
||||
use crate::peerstate::*;
|
||||
use crate::securejoin::{self, handle_securejoin_handshake};
|
||||
use crate::sql;
|
||||
use crate::stock::StockMessage;
|
||||
use crate::{contact, location};
|
||||
|
||||
@@ -54,7 +53,7 @@ pub async fn dc_receive_imf(
|
||||
println!("{}", String::from_utf8_lossy(imf_raw));
|
||||
}
|
||||
|
||||
let mut mime_parser = MimeMessage::from_bytes(context, imf_raw)?;
|
||||
let mut mime_parser = MimeMessage::from_bytes(context, imf_raw).await?;
|
||||
|
||||
// we can not add even an empty record if we have no info whatsoever
|
||||
ensure!(mime_parser.has_headers(), "No Headers Found");
|
||||
@@ -183,7 +182,7 @@ pub async fn dc_receive_imf(
|
||||
}
|
||||
|
||||
if let Some(avatar_action) = &mime_parser.user_avatar {
|
||||
match contact::set_profile_image(&context, from_id, avatar_action) {
|
||||
match contact::set_profile_image(&context, from_id, avatar_action).await {
|
||||
Ok(()) => {
|
||||
context.call_cb(Event::ChatModified(chat_id));
|
||||
}
|
||||
@@ -317,7 +316,7 @@ async fn add_parts(
|
||||
// incoming non-chat messages may be discarded
|
||||
let mut allow_creation = true;
|
||||
let show_emails =
|
||||
ShowEmails::from_i32(context.get_config_int(Config::ShowEmails)).unwrap_or_default();
|
||||
ShowEmails::from_i32(context.get_config_int(Config::ShowEmails).await).unwrap_or_default();
|
||||
if mime_parser.is_system_message != SystemMessage::AutocryptSetupMessage
|
||||
&& msgrmsg == MessengerMessage::No
|
||||
{
|
||||
@@ -377,7 +376,9 @@ async fn add_parts(
|
||||
}
|
||||
|
||||
let (test_normal_chat_id, test_normal_chat_id_blocked) =
|
||||
chat::lookup_by_contact_id(context, from_id).unwrap_or_default();
|
||||
chat::lookup_by_contact_id(context, from_id)
|
||||
.await
|
||||
.unwrap_or_default();
|
||||
|
||||
// get the chat_id - a chat_id here is no indicator that the chat is displayed in the normal list,
|
||||
// it might also be blocked and displayed in the deaddrop as a result
|
||||
@@ -399,7 +400,8 @@ async fn add_parts(
|
||||
create_blocked,
|
||||
from_id,
|
||||
to_ids,
|
||||
)?;
|
||||
)
|
||||
.await?;
|
||||
*chat_id = new_chat_id;
|
||||
chat_id_blocked = new_chat_id_blocked;
|
||||
if !chat_id.is_unset()
|
||||
@@ -433,6 +435,7 @@ async fn add_parts(
|
||||
} else if allow_creation {
|
||||
let (id, bl) =
|
||||
chat::create_or_lookup_by_contact_id(context, from_id, create_blocked)
|
||||
.await
|
||||
.unwrap_or_default();
|
||||
*chat_id = id;
|
||||
chat_id_blocked = bl;
|
||||
@@ -487,7 +490,8 @@ async fn add_parts(
|
||||
Blocked::Not,
|
||||
from_id,
|
||||
to_ids,
|
||||
)?;
|
||||
)
|
||||
.await?;
|
||||
*chat_id = new_chat_id;
|
||||
chat_id_blocked = new_chat_id_blocked;
|
||||
// automatically unblock chat when the user sends a message
|
||||
@@ -498,13 +502,14 @@ async fn add_parts(
|
||||
}
|
||||
if chat_id.is_unset() && allow_creation {
|
||||
let create_blocked = if MessengerMessage::No != msgrmsg
|
||||
&& !Contact::is_blocked_load(context, to_id)
|
||||
&& !Contact::is_blocked_load(context, to_id).await
|
||||
{
|
||||
Blocked::Not
|
||||
} else {
|
||||
Blocked::Deaddrop
|
||||
};
|
||||
let (id, bl) = chat::create_or_lookup_by_contact_id(context, to_id, create_blocked)
|
||||
.await
|
||||
.unwrap_or_default();
|
||||
*chat_id = id;
|
||||
chat_id_blocked = bl;
|
||||
@@ -527,6 +532,7 @@ async fn add_parts(
|
||||
// maybe an Autocrypt Setup Message
|
||||
let (id, bl) =
|
||||
chat::create_or_lookup_by_contact_id(context, DC_CONTACT_ID_SELF, Blocked::Not)
|
||||
.await
|
||||
.unwrap_or_default();
|
||||
*chat_id = id;
|
||||
chat_id_blocked = bl;
|
||||
@@ -554,11 +560,11 @@ async fn add_parts(
|
||||
);
|
||||
|
||||
// unarchive chat
|
||||
chat_id.unarchive(context)?;
|
||||
chat_id.unarchive(context).await?;
|
||||
|
||||
// if the mime-headers should be saved, find out its size
|
||||
// (the mime-header ends with an empty line)
|
||||
let save_mime_headers = context.get_config_bool(Config::SaveMimeHeaders);
|
||||
let save_mime_headers = context.get_config_bool(Config::SaveMimeHeaders).await;
|
||||
if let Some(raw) = mime_parser.get(HeaderDef::InReplyTo) {
|
||||
mime_in_reply_to = raw.clone();
|
||||
}
|
||||
@@ -573,18 +579,21 @@ async fn add_parts(
|
||||
// (eg. one per attachment))
|
||||
let icnt = mime_parser.parts.len();
|
||||
|
||||
let mut txt_raw = None;
|
||||
context
|
||||
.sql
|
||||
.with_conn(|mut conn| {
|
||||
let subject = mime_parser.get_subject().unwrap_or_default();
|
||||
let mut txt_raw = None;
|
||||
|
||||
context.sql.prepare(
|
||||
"INSERT INTO msgs \
|
||||
for part in mime_parser.parts.iter_mut() {
|
||||
let mut stmt = conn.prepare_cached(
|
||||
"INSERT INTO msgs \
|
||||
(rfc724_mid, server_folder, server_uid, chat_id, from_id, to_id, timestamp, \
|
||||
timestamp_sent, timestamp_rcvd, type, state, msgrmsg, txt, txt_raw, param, \
|
||||
bytes, hidden, mime_headers, mime_in_reply_to, mime_references) \
|
||||
VALUES (?,?,?,?,?,?, ?,?,?,?,?,?, ?,?,?,?,?,?, ?,?);",
|
||||
|mut stmt, conn| {
|
||||
let subject = mime_parser.get_subject().unwrap_or_default();
|
||||
)?;
|
||||
|
||||
for part in mime_parser.parts.iter_mut() {
|
||||
if mime_parser.location_kml.is_some()
|
||||
&& icnt == 1
|
||||
&& (part.msg == "-location-" || part.msg.is_empty())
|
||||
@@ -633,14 +642,17 @@ async fn add_parts(
|
||||
])?;
|
||||
|
||||
txt_raw = None;
|
||||
|
||||
// This is okay, as we use a cached prepared statement.
|
||||
drop(stmt);
|
||||
let row_id =
|
||||
sql::get_rowid_with_conn(context, conn, "msgs", "rfc724_mid", &rfc724_mid);
|
||||
crate::sql::get_rowid(context, &mut conn, "msgs", "rfc724_mid", &rfc724_mid)?;
|
||||
*insert_msg_id = MsgId::new(row_id);
|
||||
created_db_entries.push((*chat_id, *insert_msg_id));
|
||||
}
|
||||
Ok(())
|
||||
},
|
||||
)?;
|
||||
})
|
||||
.await?;
|
||||
|
||||
info!(
|
||||
context,
|
||||
@@ -763,7 +775,7 @@ fn calc_timestamps(
|
||||
///
|
||||
/// on success the function returns the found/created (chat_id, chat_blocked) tuple .
|
||||
#[allow(non_snake_case, clippy::cognitive_complexity)]
|
||||
fn create_or_lookup_group(
|
||||
async fn create_or_lookup_group(
|
||||
context: &Context,
|
||||
mime_parser: &mut MimeMessage,
|
||||
allow_creation: bool,
|
||||
@@ -827,6 +839,7 @@ fn create_or_lookup_group(
|
||||
X_MrRemoveFromGrp = Some(optional_field);
|
||||
mime_parser.is_system_message = SystemMessage::MemberRemovedFromGroup;
|
||||
let left_group = Contact::lookup_id_by_addr(context, X_MrRemoveFromGrp.as_ref().unwrap())
|
||||
.await
|
||||
== from_id as u32;
|
||||
better_msg = context.stock_system_msg(
|
||||
if left_group {
|
||||
@@ -889,6 +902,7 @@ fn create_or_lookup_group(
|
||||
|
||||
// check, if we have a chat with this group ID
|
||||
let (mut chat_id, chat_id_verified, _blocked) = chat::get_chat_id_by_grpid(context, &grpid)
|
||||
.await
|
||||
.unwrap_or((ChatId::new(0), false, Blocked::Not));
|
||||
if !chat_id.is_error() {
|
||||
if chat_id_verified {
|
||||
@@ -900,7 +914,7 @@ fn create_or_lookup_group(
|
||||
mime_parser.repl_msg_by_error(s);
|
||||
}
|
||||
}
|
||||
if !chat::is_contact_in_chat(context, chat_id, from_id as u32) {
|
||||
if !chat::is_contact_in_chat(context, chat_id, from_id as u32).await {
|
||||
// The From-address is not part of this group.
|
||||
// It could be a new user or a DSN from a mailer-daemon.
|
||||
// in any case we do not want to recreate the member list
|
||||
@@ -913,9 +927,12 @@ fn create_or_lookup_group(
|
||||
}
|
||||
|
||||
// check if the group does not exist but should be created
|
||||
let group_explicitly_left = chat::is_group_explicitly_left(context, &grpid).unwrap_or_default();
|
||||
let group_explicitly_left = chat::is_group_explicitly_left(context, &grpid)
|
||||
.await
|
||||
.unwrap_or_default();
|
||||
let self_addr = context
|
||||
.get_config(Config::ConfiguredAddr)
|
||||
.await
|
||||
.unwrap_or_default();
|
||||
|
||||
if chat_id.is_error()
|
||||
@@ -953,7 +970,8 @@ fn create_or_lookup_group(
|
||||
grpname.as_ref().unwrap(),
|
||||
create_blocked,
|
||||
create_verified,
|
||||
);
|
||||
)
|
||||
.await;
|
||||
chat_id_blocked = create_blocked;
|
||||
recreate_member_list = true;
|
||||
}
|
||||
@@ -997,13 +1015,14 @@ fn create_or_lookup_group(
|
||||
if let Some(ref grpname) = grpname {
|
||||
if grpname.len() < 200 {
|
||||
info!(context, "updating grpname for chat {}", chat_id);
|
||||
if sql::execute(
|
||||
context,
|
||||
&context.sql,
|
||||
"UPDATE chats SET name=? WHERE id=?;",
|
||||
params![grpname, chat_id],
|
||||
)
|
||||
.is_ok()
|
||||
if context
|
||||
.sql
|
||||
.execute(
|
||||
"UPDATE chats SET name=? WHERE id=?;",
|
||||
params![grpname, chat_id],
|
||||
)
|
||||
.await
|
||||
.is_ok()
|
||||
{
|
||||
context.call_cb(Event::ChatModified(chat_id));
|
||||
}
|
||||
@@ -1012,7 +1031,7 @@ fn create_or_lookup_group(
|
||||
}
|
||||
if let Some(avatar_action) = &mime_parser.group_avatar {
|
||||
info!(context, "group-avatar change for {}", chat_id);
|
||||
if let Ok(mut chat) = Chat::load_from_db(context, chat_id) {
|
||||
if let Ok(mut chat) = Chat::load_from_db(context, chat_id).await {
|
||||
match avatar_action {
|
||||
AvatarAction::Change(profile_image) => {
|
||||
chat.param.set(Param::ProfileImage, profile_image);
|
||||
@@ -1021,33 +1040,33 @@ fn create_or_lookup_group(
|
||||
chat.param.remove(Param::ProfileImage);
|
||||
}
|
||||
};
|
||||
chat.update_param(context)?;
|
||||
chat.update_param(context).await?;
|
||||
send_EVENT_CHAT_MODIFIED = true;
|
||||
}
|
||||
}
|
||||
|
||||
// add members to group/check members
|
||||
if recreate_member_list {
|
||||
if !chat::is_contact_in_chat(context, chat_id, DC_CONTACT_ID_SELF) {
|
||||
chat::add_to_chat_contacts_table(context, chat_id, DC_CONTACT_ID_SELF);
|
||||
if !chat::is_contact_in_chat(context, chat_id, DC_CONTACT_ID_SELF).await {
|
||||
chat::add_to_chat_contacts_table(context, chat_id, DC_CONTACT_ID_SELF).await;
|
||||
}
|
||||
if from_id > DC_CONTACT_ID_LAST_SPECIAL
|
||||
&& !Contact::addr_equals_contact(context, &self_addr, from_id as u32)
|
||||
&& !chat::is_contact_in_chat(context, chat_id, from_id)
|
||||
&& !Contact::addr_equals_contact(context, &self_addr, from_id as u32).await
|
||||
&& !chat::is_contact_in_chat(context, chat_id, from_id).await
|
||||
{
|
||||
chat::add_to_chat_contacts_table(context, chat_id, from_id as u32);
|
||||
chat::add_to_chat_contacts_table(context, chat_id, from_id as u32).await;
|
||||
}
|
||||
for &to_id in to_ids.iter() {
|
||||
info!(context, "adding to={:?} to chat id={}", to_id, chat_id);
|
||||
if !Contact::addr_equals_contact(context, &self_addr, to_id)
|
||||
&& !chat::is_contact_in_chat(context, chat_id, to_id)
|
||||
if !Contact::addr_equals_contact(context, &self_addr, to_id).await
|
||||
&& !chat::is_contact_in_chat(context, chat_id, to_id).await
|
||||
{
|
||||
chat::add_to_chat_contacts_table(context, chat_id, to_id);
|
||||
}
|
||||
}
|
||||
send_EVENT_CHAT_MODIFIED = true;
|
||||
} else if let Some(removed_addr) = X_MrRemoveFromGrp {
|
||||
let contact_id = Contact::lookup_id_by_addr(context, removed_addr);
|
||||
let contact_id = Contact::lookup_id_by_addr(context, removed_addr).await;
|
||||
if contact_id != 0 {
|
||||
info!(context, "remove {:?} from chat id={}", contact_id, chat_id);
|
||||
chat::remove_from_chat_contacts_table(context, chat_id, contact_id);
|
||||
@@ -1178,16 +1197,14 @@ fn create_or_lookup_adhoc_group(
|
||||
Ok((new_chat_id, create_blocked))
|
||||
}
|
||||
|
||||
fn create_group_record(
|
||||
async fn create_group_record(
|
||||
context: &Context,
|
||||
grpid: impl AsRef<str>,
|
||||
grpname: impl AsRef<str>,
|
||||
create_blocked: Blocked,
|
||||
create_verified: VerifiedStatus,
|
||||
) -> ChatId {
|
||||
if sql::execute(
|
||||
context,
|
||||
&context.sql,
|
||||
if context.sql.execute(
|
||||
"INSERT INTO chats (type, name, grpid, blocked, created_timestamp) VALUES(?, ?, ?, ?, ?);",
|
||||
params![
|
||||
if VerifiedStatus::Unverified != create_verified {
|
||||
@@ -1200,7 +1217,7 @@ fn create_group_record(
|
||||
create_blocked,
|
||||
time(),
|
||||
],
|
||||
)
|
||||
).await
|
||||
.is_err()
|
||||
{
|
||||
warn!(
|
||||
@@ -1211,7 +1228,12 @@ fn create_group_record(
|
||||
);
|
||||
return ChatId::new(0);
|
||||
}
|
||||
let row_id = sql::get_rowid(context, &context.sql, "chats", "grpid", grpid.as_ref());
|
||||
let row_id = context
|
||||
.sql
|
||||
.get_rowid(context, "chats", "grpid", grpid.as_ref())
|
||||
.await
|
||||
.unwrap_or_default();
|
||||
|
||||
let chat_id = ChatId::new(row_id);
|
||||
info!(
|
||||
context,
|
||||
|
||||
64
src/e2ee.rs
64
src/e2ee.rs
@@ -27,18 +27,18 @@ pub struct EncryptHelper {
|
||||
}
|
||||
|
||||
impl EncryptHelper {
|
||||
pub fn new(context: &Context) -> Result<EncryptHelper> {
|
||||
pub async fn new(context: &Context) -> Result<EncryptHelper> {
|
||||
let prefer_encrypt =
|
||||
EncryptPreference::from_i32(context.get_config_int(Config::E2eeEnabled))
|
||||
EncryptPreference::from_i32(context.get_config_int(Config::E2eeEnabled).await)
|
||||
.unwrap_or_default();
|
||||
let addr = match context.get_config(Config::ConfiguredAddr) {
|
||||
let addr = match context.get_config(Config::ConfiguredAddr).await {
|
||||
None => {
|
||||
bail!("addr not configured!");
|
||||
}
|
||||
Some(addr) => addr,
|
||||
};
|
||||
|
||||
let public_key = load_or_generate_self_public_key(context, &addr)?;
|
||||
let public_key = load_or_generate_self_public_key(context, &addr).await?;
|
||||
|
||||
Ok(EncryptHelper {
|
||||
prefer_encrypt,
|
||||
@@ -88,12 +88,12 @@ impl EncryptHelper {
|
||||
}
|
||||
|
||||
/// Tries to encrypt the passed in `mail`.
|
||||
pub fn encrypt(
|
||||
pub async fn encrypt(
|
||||
&mut self,
|
||||
context: &Context,
|
||||
min_verified: PeerstateVerifiedStatus,
|
||||
mail_to_encrypt: lettre_email::PartBuilder,
|
||||
peerstates: &[(Option<Peerstate>, &str)],
|
||||
peerstates: &[(Option<Peerstate<'_>>, &str)],
|
||||
) -> Result<String> {
|
||||
let mut keyring = Keyring::default();
|
||||
|
||||
@@ -109,6 +109,7 @@ impl EncryptHelper {
|
||||
let public_key = Key::from(self.public_key.clone());
|
||||
keyring.add_ref(&public_key);
|
||||
let sign_key = Key::from_self_private(context, self.addr.clone(), &context.sql)
|
||||
.await
|
||||
.ok_or_else(|| format_err!("missing own private key"))?;
|
||||
|
||||
let raw_message = mail_to_encrypt.build().as_string().into_bytes();
|
||||
@@ -119,7 +120,7 @@ impl EncryptHelper {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn try_decrypt(
|
||||
pub async fn try_decrypt(
|
||||
context: &Context,
|
||||
mail: &ParsedMail<'_>,
|
||||
message_time: i64,
|
||||
@@ -141,14 +142,14 @@ pub fn try_decrypt(
|
||||
if let Some(ref mut peerstate) = peerstate {
|
||||
if let Some(ref header) = autocryptheader {
|
||||
peerstate.apply_header(&header, message_time);
|
||||
peerstate.save_to_db(&context.sql, false)?;
|
||||
peerstate.save_to_db(&context.sql, false).await?;
|
||||
} else if message_time > peerstate.last_seen_autocrypt && !contains_report(mail) {
|
||||
peerstate.degrade_encryption(message_time);
|
||||
peerstate.save_to_db(&context.sql, false)?;
|
||||
peerstate.save_to_db(&context.sql, false).await?;
|
||||
}
|
||||
} else if let Some(ref header) = autocryptheader {
|
||||
let p = Peerstate::from_header(context, header, message_time);
|
||||
p.save_to_db(&context.sql, true)?;
|
||||
p.save_to_db(&context.sql, true).await?;
|
||||
peerstate = Some(p);
|
||||
}
|
||||
}
|
||||
@@ -158,16 +159,19 @@ pub fn try_decrypt(
|
||||
let mut public_keyring_for_validate = Keyring::default();
|
||||
let mut out_mail = None;
|
||||
let mut signatures = HashSet::default();
|
||||
let self_addr = context.get_config(Config::ConfiguredAddr);
|
||||
let self_addr = context.get_config(Config::ConfiguredAddr).await;
|
||||
|
||||
if let Some(self_addr) = self_addr {
|
||||
if private_keyring.load_self_private_for_decrypting(context, self_addr, &context.sql) {
|
||||
if private_keyring
|
||||
.load_self_private_for_decrypting(context, self_addr, &context.sql)
|
||||
.await
|
||||
{
|
||||
if peerstate.as_ref().map(|p| p.last_seen).unwrap_or_else(|| 0) == 0 {
|
||||
peerstate = Peerstate::from_addr(&context, &context.sql, &from);
|
||||
}
|
||||
if let Some(ref peerstate) = peerstate {
|
||||
if peerstate.degrade_event.is_some() {
|
||||
handle_degrade_event(context, &peerstate)?;
|
||||
handle_degrade_event(context, &peerstate).await?;
|
||||
}
|
||||
if let Some(ref key) = peerstate.gossip_key {
|
||||
public_keyring_for_validate.add_ref(key);
|
||||
@@ -195,18 +199,18 @@ pub fn try_decrypt(
|
||||
/// storing a new one when one doesn't exist yet. Care is taken to
|
||||
/// only generate one key per context even when multiple threads call
|
||||
/// this function concurrently.
|
||||
fn load_or_generate_self_public_key(
|
||||
async fn load_or_generate_self_public_key(
|
||||
context: &Context,
|
||||
self_addr: impl AsRef<str>,
|
||||
) -> 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)
|
||||
.map_err(|_| Error::Message("Not a public key".into()));
|
||||
}
|
||||
let _guard = context.generating_key_mutex.lock().unwrap();
|
||||
|
||||
// Check again in case the key was generated while we were waiting for the lock.
|
||||
if let Some(key) = Key::from_self_public(context, &self_addr, &context.sql) {
|
||||
if let Some(key) = Key::from_self_public(context, &self_addr, &context.sql).await {
|
||||
return SignedPublicKey::try_from(key)
|
||||
.map_err(|_| Error::Message("Not a public key".into()));
|
||||
}
|
||||
@@ -214,10 +218,10 @@ fn load_or_generate_self_public_key(
|
||||
let start = std::time::Instant::now();
|
||||
|
||||
let keygen_type =
|
||||
KeyGenType::from_i32(context.get_config_int(Config::KeyGenType)).unwrap_or_default();
|
||||
KeyGenType::from_i32(context.get_config_int(Config::KeyGenType).await).unwrap_or_default();
|
||||
info!(context, "Generating keypair with type {}", keygen_type);
|
||||
let keypair = pgp::create_keypair(EmailAddress::new(self_addr.as_ref())?, keygen_type)?;
|
||||
key::store_self_keypair(context, &keypair, KeyPairUse::Default)?;
|
||||
key::store_self_keypair(context, &keypair, KeyPairUse::Default).await?;
|
||||
info!(
|
||||
context,
|
||||
"Keypair generated in {:.3}s.",
|
||||
@@ -347,14 +351,18 @@ fn contains_report(mail: &ParsedMail<'_>) -> bool {
|
||||
///
|
||||
/// If this succeeds you are also guaranteed that the
|
||||
/// [Config::ConfiguredAddr] is configured, this address is returned.
|
||||
pub fn ensure_secret_key_exists(context: &Context) -> Result<String> {
|
||||
let self_addr = context.get_config(Config::ConfiguredAddr).ok_or_else(|| {
|
||||
format_err!(concat!(
|
||||
"Failed to get self address, ",
|
||||
"cannot ensure secret key if not configured."
|
||||
))
|
||||
})?;
|
||||
load_or_generate_self_public_key(context, &self_addr)?;
|
||||
pub async fn ensure_secret_key_exists(context: &Context) -> Result<String> {
|
||||
let self_addr = context
|
||||
.get_config(Config::ConfiguredAddr)
|
||||
.await
|
||||
.ok_or_else(|| {
|
||||
format_err!(concat!(
|
||||
"Failed to get self address, ",
|
||||
"cannot ensure secret key if not configured."
|
||||
))
|
||||
})?;
|
||||
load_or_generate_self_public_key(context, &self_addr).await?;
|
||||
|
||||
Ok(self_addr)
|
||||
}
|
||||
|
||||
@@ -371,7 +379,7 @@ mod tests {
|
||||
async fn test_prexisting() {
|
||||
let t = dummy_context();
|
||||
let test_addr = configure_alice_keypair(&t.ctx).await;
|
||||
assert_eq!(ensure_secret_key_exists(&t.ctx).unwrap(), test_addr);
|
||||
assert_eq!(ensure_secret_key_exists(&t.ctx).await.unwrap(), test_addr);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -412,7 +420,7 @@ Sent with my Delta Chat Messenger: https://delta.chat";
|
||||
async fn test_existing() {
|
||||
let t = dummy_context();
|
||||
let addr = configure_alice_keypair(&t.ctx).await;
|
||||
let key = load_or_generate_self_public_key(&t.ctx, addr);
|
||||
let key = load_or_generate_self_public_key(&t.ctx, addr).await;
|
||||
assert!(key.is_ok());
|
||||
}
|
||||
|
||||
|
||||
@@ -367,11 +367,11 @@ impl Imap {
|
||||
if self.is_connected().await && !self.should_reconnect() {
|
||||
return Ok(());
|
||||
}
|
||||
if !context.sql.get_raw_config_bool(context, "configured") {
|
||||
if !context.sql.get_raw_config_bool(context, "configured").await {
|
||||
return Err(Error::ConnectWithoutConfigure);
|
||||
}
|
||||
|
||||
let param = LoginParam::from_database(context, "configured_");
|
||||
let param = LoginParam::from_database(context, "configured_").await;
|
||||
// the trailing underscore is correct
|
||||
|
||||
if self.connect(context, ¶m).await {
|
||||
@@ -415,7 +415,7 @@ impl Imap {
|
||||
let teardown = match &mut *self.session.lock().await {
|
||||
Some(ref mut session) => match session.capabilities().await {
|
||||
Ok(caps) => {
|
||||
if !context.sql.is_open() {
|
||||
if !context.sql.is_open().await {
|
||||
warn!(context, "IMAP-LOGIN as {} ok but ABORTING", lp.mail_user,);
|
||||
true
|
||||
} else {
|
||||
@@ -465,7 +465,7 @@ impl Imap {
|
||||
}
|
||||
|
||||
pub async fn fetch(&self, context: &Context, watch_folder: &str) -> Result<()> {
|
||||
if !context.sql.is_open() {
|
||||
if !context.sql.is_open().await {
|
||||
// probably shutdown
|
||||
return Err(Error::InTeardown);
|
||||
}
|
||||
@@ -477,9 +477,13 @@ impl Imap {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_config_last_seen_uid<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());
|
||||
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>`
|
||||
let mut parts = entry.split(':');
|
||||
(
|
||||
@@ -508,7 +512,7 @@ impl Imap {
|
||||
self.select_folder(context, Some(folder)).await?;
|
||||
|
||||
// compare last seen UIDVALIDITY against the current one
|
||||
let (uid_validity, last_seen_uid) = self.get_config_last_seen_uid(context, &folder);
|
||||
let (uid_validity, last_seen_uid) = self.get_config_last_seen_uid(context, &folder).await;
|
||||
|
||||
let config = self.config.read().await;
|
||||
let mailbox = config
|
||||
@@ -590,8 +594,8 @@ impl Imap {
|
||||
context: &Context,
|
||||
folder: S,
|
||||
) -> Result<bool> {
|
||||
let show_emails =
|
||||
ShowEmails::from_i32(context.get_config_int(Config::ShowEmails)).unwrap_or_default();
|
||||
let show_emails = ShowEmails::from_i32(context.get_config_int(Config::ShowEmails).await)
|
||||
.unwrap_or_default();
|
||||
|
||||
let (uid_validity, last_seen_uid) = self
|
||||
.select_with_uidvalidity(context, folder.as_ref())
|
||||
@@ -706,7 +710,7 @@ impl Imap {
|
||||
Ok(read_cnt > 0)
|
||||
}
|
||||
|
||||
fn set_config_last_seen_uid<S: AsRef<str>>(
|
||||
async fn set_config_last_seen_uid<S: AsRef<str>>(
|
||||
&self,
|
||||
context: &Context,
|
||||
folder: S,
|
||||
@@ -716,7 +720,11 @@ impl Imap {
|
||||
let key = format!("imap.mailbox.{}", folder.as_ref());
|
||||
let val = format!("{}:{}", uidvalidity, lastseenuid);
|
||||
|
||||
context.sql.set_raw_config(context, &key, Some(&val)).ok();
|
||||
context
|
||||
.sql
|
||||
.set_raw_config(context, &key, Some(&val))
|
||||
.await
|
||||
.ok();
|
||||
}
|
||||
|
||||
/// Fetches a single message by server UID.
|
||||
@@ -1080,7 +1088,8 @@ impl Imap {
|
||||
) -> Result<()> {
|
||||
let folders_configured = context
|
||||
.sql
|
||||
.get_raw_config_int(context, "folders_configured");
|
||||
.get_raw_config_int(context, "folders_configured")
|
||||
.await;
|
||||
if folders_configured.unwrap_or_default() >= 3 {
|
||||
// the "3" here we increase if we have future updates to
|
||||
// to folder configuration
|
||||
@@ -1168,24 +1177,28 @@ impl Imap {
|
||||
}
|
||||
context
|
||||
.sql
|
||||
.set_raw_config(context, "configured_inbox_folder", Some("INBOX"))?;
|
||||
.set_raw_config(context, "configured_inbox_folder", Some("INBOX"))
|
||||
.await?;
|
||||
if let Some(ref mvbox_folder) = mvbox_folder {
|
||||
context.sql.set_raw_config(
|
||||
context,
|
||||
"configured_mvbox_folder",
|
||||
Some(mvbox_folder),
|
||||
)?;
|
||||
context
|
||||
.sql
|
||||
.set_raw_config(context, "configured_mvbox_folder", Some(mvbox_folder))
|
||||
.await?;
|
||||
}
|
||||
if let Some(ref sentbox_folder) = sentbox_folder {
|
||||
context.sql.set_raw_config(
|
||||
context,
|
||||
"configured_sentbox_folder",
|
||||
Some(sentbox_folder.name()),
|
||||
)?;
|
||||
context
|
||||
.sql
|
||||
.set_raw_config(
|
||||
context,
|
||||
"configured_sentbox_folder",
|
||||
Some(sentbox_folder.name()),
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
context
|
||||
.sql
|
||||
.set_raw_config_int(context, "folders_configured", 3)?;
|
||||
.set_raw_config_int(context, "folders_configured", 3)
|
||||
.await?;
|
||||
}
|
||||
info!(context, "FINISHED configuring IMAP-folders.");
|
||||
Ok(())
|
||||
|
||||
306
src/imex.rs
306
src/imex.rs
@@ -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)
|
||||
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_iter = std::fs::read_dir(dir_name)?;
|
||||
let mut newest_backup_time = 0;
|
||||
@@ -93,16 +93,17 @@ pub fn has_backup(context: &Context, dir_name: impl AsRef<Path>) -> Result<Strin
|
||||
let name = name.to_string_lossy();
|
||||
if name.starts_with("delta-chat") && name.ends_with(".bak") {
|
||||
let sql = Sql::new();
|
||||
if sql.open(context, &path, true) {
|
||||
if sql.open(context, &path, true).await {
|
||||
let curr_backup_time = sql
|
||||
.get_raw_config_int(context, "backup_time")
|
||||
.await
|
||||
.unwrap_or_default();
|
||||
if curr_backup_time > newest_backup_time {
|
||||
newest_backup_path = Some(path);
|
||||
newest_backup_time = curr_backup_time;
|
||||
}
|
||||
info!(context, "backup_time of {} is {}", name, curr_backup_time);
|
||||
sql.close(&context);
|
||||
sql.close(&context).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -125,7 +126,7 @@ async fn do_initiate_key_transfer(context: &Context) -> Result<String> {
|
||||
let setup_code = create_setup_code(context);
|
||||
/* this may require a keypair to be created. this may take a second ... */
|
||||
ensure!(!context.shall_stop_ongoing(), "canceled");
|
||||
let setup_file_content = render_setup_file(context, &setup_code)?;
|
||||
let setup_file_content = render_setup_file(context, &setup_code).await?;
|
||||
/* encrypting may also take a while ... */
|
||||
ensure!(!context.shall_stop_ongoing(), "canceled");
|
||||
let setup_file_blob = BlobObject::create(
|
||||
@@ -134,7 +135,7 @@ async fn do_initiate_key_transfer(context: &Context) -> Result<String> {
|
||||
setup_file_content.as_bytes(),
|
||||
)?;
|
||||
|
||||
let chat_id = chat::create_by_contact_id(context, DC_CONTACT_ID_SELF)?;
|
||||
let chat_id = chat::create_by_contact_id(context, DC_CONTACT_ID_SELF).await?;
|
||||
msg = Message::default();
|
||||
msg.viewtype = Viewtype::File;
|
||||
msg.param.set(Param::File, setup_file_blob.as_name());
|
||||
@@ -152,7 +153,7 @@ async fn do_initiate_key_transfer(context: &Context) -> Result<String> {
|
||||
info!(context, "Wait for setup message being sent ...",);
|
||||
while !context.shall_stop_ongoing() {
|
||||
std::thread::sleep(std::time::Duration::from_secs(1));
|
||||
if let Ok(msg) = Message::load_from_db(context, msg_id) {
|
||||
if let Ok(msg) = Message::load_from_db(context, msg_id).await {
|
||||
if msg.is_sent() {
|
||||
info!(context, "... setup message sent.",);
|
||||
break;
|
||||
@@ -170,15 +171,16 @@ async fn do_initiate_key_transfer(context: &Context) -> Result<String> {
|
||||
/// Renders HTML body of a setup file message.
|
||||
///
|
||||
/// The `passphrase` must be at least 2 characters long.
|
||||
pub fn render_setup_file(context: &Context, passphrase: &str) -> Result<String> {
|
||||
pub async fn render_setup_file(context: &Context, passphrase: &str) -> Result<String> {
|
||||
ensure!(
|
||||
passphrase.len() >= 2,
|
||||
"Passphrase must be at least 2 chars long."
|
||||
);
|
||||
let self_addr = e2ee::ensure_secret_key_exists(context)?;
|
||||
let self_addr = e2ee::ensure_secret_key_exists(context).await?;
|
||||
let private_key = Key::from_self_private(context, self_addr, &context.sql)
|
||||
.await
|
||||
.ok_or_else(|| format_err!("Failed to get private key."))?;
|
||||
let ac_headers = match context.get_config_bool(Config::E2eeEnabled) {
|
||||
let ac_headers = match context.get_config_bool(Config::E2eeEnabled).await {
|
||||
false => None,
|
||||
true => Some(("Autocrypt-Prefer-Encrypt", "mutual")),
|
||||
};
|
||||
@@ -239,8 +241,8 @@ pub fn create_setup_code(_context: &Context) -> String {
|
||||
ret
|
||||
}
|
||||
|
||||
fn maybe_add_bcc_self_device_msg(context: &Context) -> Result<()> {
|
||||
if !context.sql.get_raw_config_bool(context, "bcc_self") {
|
||||
async fn maybe_add_bcc_self_device_msg(context: &Context) -> Result<()> {
|
||||
if !context.sql.get_raw_config_bool(context, "bcc_self").await {
|
||||
let mut msg = Message::new(Viewtype::Text);
|
||||
// TODO: define this as a stockstring once the wording is settled.
|
||||
msg.text = Some(
|
||||
@@ -249,15 +251,19 @@ fn maybe_add_bcc_self_device_msg(context: &Context) -> Result<()> {
|
||||
go to the settings and enable \"Send copy to self\"."
|
||||
.to_string(),
|
||||
);
|
||||
chat::add_device_msg(context, Some("bcc-self-hint"), Some(&mut msg))?;
|
||||
chat::add_device_msg(context, Some("bcc-self-hint"), Some(&mut msg)).await?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn continue_key_transfer(context: &Context, msg_id: MsgId, setup_code: &str) -> Result<()> {
|
||||
pub async fn continue_key_transfer(
|
||||
context: &Context,
|
||||
msg_id: MsgId,
|
||||
setup_code: &str,
|
||||
) -> Result<()> {
|
||||
ensure!(!msg_id.is_special(), "wrong id");
|
||||
|
||||
let msg = Message::load_from_db(context, msg_id)?;
|
||||
let msg = Message::load_from_db(context, msg_id).await?;
|
||||
ensure!(
|
||||
msg.is_setupmessage(),
|
||||
"Message is no Autocrypt Setup Message."
|
||||
@@ -267,8 +273,8 @@ pub fn continue_key_transfer(context: &Context, msg_id: MsgId, setup_code: &str)
|
||||
let file = dc_open_file(context, filename)?;
|
||||
let sc = normalize_setup_code(setup_code);
|
||||
let armored_key = decrypt_setup_file(context, &sc, file)?;
|
||||
set_self_key(context, &armored_key, true, true)?;
|
||||
maybe_add_bcc_self_device_msg(context)?;
|
||||
set_self_key(context, &armored_key, true, true).await?;
|
||||
maybe_add_bcc_self_device_msg(context).await?;
|
||||
|
||||
Ok(())
|
||||
} else {
|
||||
@@ -276,7 +282,7 @@ pub fn continue_key_transfer(context: &Context, msg_id: MsgId, setup_code: &str)
|
||||
}
|
||||
}
|
||||
|
||||
fn set_self_key(
|
||||
async fn set_self_key(
|
||||
context: &Context,
|
||||
armored: &str,
|
||||
set_default: bool,
|
||||
@@ -301,7 +307,8 @@ fn set_self_key(
|
||||
};
|
||||
context
|
||||
.sql
|
||||
.set_raw_config_int(context, "e2ee_enabled", e2ee_enabled)?;
|
||||
.set_raw_config_int(context, "e2ee_enabled", e2ee_enabled)
|
||||
.await?;
|
||||
}
|
||||
None => {
|
||||
if prefer_encrypt_required {
|
||||
@@ -310,7 +317,7 @@ fn set_self_key(
|
||||
}
|
||||
};
|
||||
|
||||
let self_addr = context.get_config(Config::ConfiguredAddr);
|
||||
let self_addr = context.get_config(Config::ConfiguredAddr).await;
|
||||
ensure!(self_addr.is_some(), "Missing self addr");
|
||||
let addr = EmailAddress::new(&self_addr.unwrap_or_default())?;
|
||||
|
||||
@@ -331,7 +338,8 @@ fn set_self_key(
|
||||
} else {
|
||||
key::KeyPairUse::ReadOnly
|
||||
},
|
||||
)?;
|
||||
)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -359,8 +367,7 @@ pub fn normalize_setup_code(s: &str) -> String {
|
||||
out
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
pub fn JobImexImap(context: &Context, job: &Job) -> Result<()> {
|
||||
pub async fn job_imex_imap(context: &Context, job: &Job) -> Result<()> {
|
||||
ensure!(context.alloc_ongoing(), "could not allocate ongoing");
|
||||
let what: Option<ImexMode> = job.param.get_int(Param::Cmd).and_then(ImexMode::from_i32);
|
||||
let param = job.param.get(Param::Arg).unwrap_or_default();
|
||||
@@ -369,10 +376,10 @@ pub fn JobImexImap(context: &Context, job: &Job) -> Result<()> {
|
||||
info!(context, "Import/export process started.");
|
||||
context.call_cb(Event::ImexProgress(10));
|
||||
|
||||
ensure!(context.sql.is_open(), "Database not opened.");
|
||||
ensure!(context.sql.is_open().await, "Database not opened.");
|
||||
if what == Some(ImexMode::ExportBackup) || what == Some(ImexMode::ExportSelfKeys) {
|
||||
// before we export anything, make sure the private key exists
|
||||
if e2ee::ensure_secret_key_exists(context).is_err() {
|
||||
if e2ee::ensure_secret_key_exists(context).await.is_err() {
|
||||
context.free_ongoing();
|
||||
bail!("Cannot create private key or private key not available.");
|
||||
} else {
|
||||
@@ -381,10 +388,10 @@ pub fn JobImexImap(context: &Context, job: &Job) -> Result<()> {
|
||||
}
|
||||
let path = Path::new(param);
|
||||
let success = match what {
|
||||
Some(ImexMode::ExportSelfKeys) => export_self_keys(context, path),
|
||||
Some(ImexMode::ImportSelfKeys) => import_self_keys(context, path),
|
||||
Some(ImexMode::ExportBackup) => export_backup(context, path),
|
||||
Some(ImexMode::ImportBackup) => import_backup(context, path),
|
||||
Some(ImexMode::ExportSelfKeys) => export_self_keys(context, path).await,
|
||||
Some(ImexMode::ImportSelfKeys) => import_self_keys(context, path).await,
|
||||
Some(ImexMode::ExportBackup) => export_backup(context, path).await,
|
||||
Some(ImexMode::ImportBackup) => import_backup(context, path).await,
|
||||
None => {
|
||||
bail!("unknown IMEX type");
|
||||
}
|
||||
@@ -404,7 +411,7 @@ pub fn JobImexImap(context: &Context, job: &Job) -> Result<()> {
|
||||
}
|
||||
|
||||
/// Import Backup
|
||||
fn import_backup(context: &Context, backup_to_import: impl AsRef<Path>) -> Result<()> {
|
||||
async fn import_backup(context: &Context, backup_to_import: impl AsRef<Path>) -> Result<()> {
|
||||
info!(
|
||||
context,
|
||||
"Import \"{}\" to \"{}\".",
|
||||
@@ -413,7 +420,7 @@ fn import_backup(context: &Context, backup_to_import: impl AsRef<Path>) -> Resul
|
||||
);
|
||||
|
||||
ensure!(
|
||||
!context.is_configured(),
|
||||
!context.is_configured().await,
|
||||
"Cannot import backups to accounts in use."
|
||||
);
|
||||
context.sql.close(&context);
|
||||
@@ -430,61 +437,71 @@ fn import_backup(context: &Context, backup_to_import: impl AsRef<Path>) -> Resul
|
||||
/* error already logged */
|
||||
/* re-open copied database file */
|
||||
ensure!(
|
||||
context.sql.open(&context, &context.get_dbfile(), false),
|
||||
context
|
||||
.sql
|
||||
.open(&context, &context.get_dbfile(), false)
|
||||
.await,
|
||||
"could not re-open db"
|
||||
);
|
||||
|
||||
delete_and_reset_all_device_msgs(&context)?;
|
||||
delete_and_reset_all_device_msgs(&context).await?;
|
||||
|
||||
let total_files_cnt = context
|
||||
.sql
|
||||
.query_get_value::<_, isize>(context, "SELECT COUNT(*) FROM backup_blobs;", params![])
|
||||
.await
|
||||
.unwrap_or_default() as usize;
|
||||
info!(
|
||||
context,
|
||||
"***IMPORT-in-progress: total_files_cnt={:?}", total_files_cnt,
|
||||
);
|
||||
|
||||
let res = context.sql.query_map(
|
||||
"SELECT file_name, file_content FROM backup_blobs ORDER BY id;",
|
||||
params![],
|
||||
|row| {
|
||||
let name: String = row.get(0)?;
|
||||
let blob: Vec<u8> = row.get(1)?;
|
||||
let res = context
|
||||
.sql
|
||||
.query_map(
|
||||
"SELECT file_name, file_content FROM backup_blobs ORDER BY id;",
|
||||
params![],
|
||||
|row| {
|
||||
let name: String = row.get(0)?;
|
||||
let blob: Vec<u8> = row.get(1)?;
|
||||
|
||||
Ok((name, blob))
|
||||
},
|
||||
|files| {
|
||||
for (processed_files_cnt, file) in files.enumerate() {
|
||||
let (file_name, file_blob) = file?;
|
||||
if context.shall_stop_ongoing() {
|
||||
return Ok(false);
|
||||
}
|
||||
let mut permille = processed_files_cnt * 1000 / total_files_cnt;
|
||||
if permille < 10 {
|
||||
permille = 10
|
||||
}
|
||||
if permille > 990 {
|
||||
permille = 990
|
||||
}
|
||||
context.call_cb(Event::ImexProgress(permille));
|
||||
if file_blob.is_empty() {
|
||||
continue;
|
||||
}
|
||||
Ok((name, blob))
|
||||
},
|
||||
|files| {
|
||||
for (processed_files_cnt, file) in files.enumerate() {
|
||||
let (file_name, file_blob) = file?;
|
||||
if context.shall_stop_ongoing() {
|
||||
return Ok(false);
|
||||
}
|
||||
let mut permille = processed_files_cnt * 1000 / total_files_cnt;
|
||||
if permille < 10 {
|
||||
permille = 10
|
||||
}
|
||||
if permille > 990 {
|
||||
permille = 990
|
||||
}
|
||||
context.call_cb(Event::ImexProgress(permille));
|
||||
if file_blob.is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let path_filename = context.get_blobdir().join(file_name);
|
||||
dc_write_file(context, &path_filename, &file_blob)?;
|
||||
}
|
||||
Ok(true)
|
||||
},
|
||||
);
|
||||
let path_filename = context.get_blobdir().join(file_name);
|
||||
dc_write_file(context, &path_filename, &file_blob)?;
|
||||
}
|
||||
Ok(true)
|
||||
},
|
||||
)
|
||||
.await;
|
||||
|
||||
match res {
|
||||
Ok(all_files_extracted) => {
|
||||
if all_files_extracted {
|
||||
// only delete backup_blobs if all files were successfully extracted
|
||||
sql::execute(context, &context.sql, "DROP TABLE backup_blobs;", params![])?;
|
||||
sql::try_execute(context, &context.sql, "VACUUM;").ok();
|
||||
context
|
||||
.sql
|
||||
.execute("DROP TABLE backup_blobs;", params![])
|
||||
.await?;
|
||||
context.sql.execute("VACUUM;", params![]).await.ok();
|
||||
Ok(())
|
||||
} else {
|
||||
bail!("received stop signal");
|
||||
@@ -499,7 +516,7 @@ fn import_backup(context: &Context, backup_to_import: impl AsRef<Path>) -> Resul
|
||||
******************************************************************************/
|
||||
/* the FILE_PROGRESS macro calls the callback with the permille of files processed.
|
||||
The macro avoids weird values of 0% or 100% while still working. */
|
||||
fn export_backup(context: &Context, dir: impl AsRef<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)
|
||||
// FIXME: we should write to a temporary file first and rename it on success. this would guarantee the backup is complete.
|
||||
// let dest_path_filename = dc_get_next_backup_file(context, dir, res);
|
||||
@@ -507,9 +524,9 @@ fn export_backup(context: &Context, dir: impl AsRef<Path>) -> Result<()> {
|
||||
let dest_path_filename = dc_get_next_backup_path(dir, now)?;
|
||||
let dest_path_string = dest_path_filename.to_string_lossy().to_string();
|
||||
|
||||
sql::housekeeping(context);
|
||||
sql::housekeeping(context).await;
|
||||
|
||||
sql::try_execute(context, &context.sql, "VACUUM;").ok();
|
||||
context.sql.execute("VACUUM;", params![]).await.ok();
|
||||
|
||||
// we close the database during the copy of the dbfile
|
||||
context.sql.close(context);
|
||||
@@ -531,18 +548,20 @@ fn export_backup(context: &Context, dir: impl AsRef<Path>) -> Result<()> {
|
||||
}
|
||||
let dest_sql = Sql::new();
|
||||
ensure!(
|
||||
dest_sql.open(context, &dest_path_filename, false),
|
||||
dest_sql.open(context, &dest_path_filename, false).await,
|
||||
"could not open exported database {}",
|
||||
dest_path_string
|
||||
);
|
||||
let res = match add_files_to_export(context, &dest_sql) {
|
||||
let res = match add_files_to_export(context, &dest_sql).await {
|
||||
Err(err) => {
|
||||
dc_delete_file(context, &dest_path_filename);
|
||||
error!(context, "backup failed: {}", err);
|
||||
Err(err)
|
||||
}
|
||||
Ok(()) => {
|
||||
dest_sql.set_raw_config_int(context, "backup_time", now as i32)?;
|
||||
dest_sql
|
||||
.set_raw_config_int(context, "backup_time", now as i32)
|
||||
.await?;
|
||||
context.call_cb(Event::ImexFileWritten(dest_path_filename));
|
||||
Ok(())
|
||||
}
|
||||
@@ -552,16 +571,15 @@ fn export_backup(context: &Context, dir: impl AsRef<Path>) -> Result<()> {
|
||||
Ok(res?)
|
||||
}
|
||||
|
||||
fn add_files_to_export(context: &Context, sql: &Sql) -> Result<()> {
|
||||
async fn add_files_to_export(context: &Context, sql: &Sql) -> Result<()> {
|
||||
// add all files as blobs to the database copy (this does not require
|
||||
// the source to be locked, neigher the destination as it is used only here)
|
||||
if !sql.table_exists("backup_blobs") {
|
||||
sql::execute(
|
||||
context,
|
||||
&sql,
|
||||
if !sql.table_exists("backup_blobs").await? {
|
||||
sql.execute(
|
||||
"CREATE TABLE backup_blobs (id INTEGER PRIMARY KEY, file_name, file_content);",
|
||||
params![],
|
||||
)?
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
// copy all files from BLOBDIR into backup-db
|
||||
let mut total_files_cnt = 0;
|
||||
@@ -570,47 +588,50 @@ fn add_files_to_export(context: &Context, sql: &Sql) -> Result<()> {
|
||||
total_files_cnt += dir_handle.filter(|r| r.is_ok()).count();
|
||||
|
||||
info!(context, "EXPORT: total_files_cnt={}", total_files_cnt);
|
||||
// scan directory, pass 2: copy files
|
||||
let dir_handle = std::fs::read_dir(&dir)?;
|
||||
let exported_all_files = sql.prepare(
|
||||
"INSERT INTO backup_blobs (file_name, file_content) VALUES (?, ?);",
|
||||
|mut stmt, _| {
|
||||
let mut processed_files_cnt = 0;
|
||||
for entry in dir_handle {
|
||||
let entry = entry?;
|
||||
if context.shall_stop_ongoing() {
|
||||
return Ok(false);
|
||||
}
|
||||
processed_files_cnt += 1;
|
||||
let permille = max(min(processed_files_cnt * 1000 / total_files_cnt, 990), 10);
|
||||
context.call_cb(Event::ImexProgress(permille));
|
||||
|
||||
let name_f = entry.file_name();
|
||||
let name = name_f.to_string_lossy();
|
||||
if name.starts_with("delta-chat") && name.ends_with(".bak") {
|
||||
sql.with_conn(|conn| {
|
||||
// scan directory, pass 2: copy files
|
||||
let dir_handle = std::fs::read_dir(&dir)?;
|
||||
|
||||
let mut stmt = conn
|
||||
.prepare_cached("INSERT INTO backup_blobs (file_name, file_content) VALUES (?, ?);")?;
|
||||
|
||||
let mut processed_files_cnt = 0;
|
||||
for entry in dir_handle {
|
||||
let entry = entry?;
|
||||
if context.shall_stop_ongoing() {
|
||||
return Ok(());
|
||||
}
|
||||
processed_files_cnt += 1;
|
||||
let permille = max(min(processed_files_cnt * 1000 / total_files_cnt, 990), 10);
|
||||
context.call_cb(Event::ImexProgress(permille));
|
||||
|
||||
let name_f = entry.file_name();
|
||||
let name = name_f.to_string_lossy();
|
||||
if name.starts_with("delta-chat") && name.ends_with(".bak") {
|
||||
continue;
|
||||
}
|
||||
info!(context, "EXPORT: copying filename={}", name);
|
||||
let curr_path_filename = context.get_blobdir().join(entry.file_name());
|
||||
if let Ok(buf) = dc_read_file(context, &curr_path_filename) {
|
||||
if buf.is_empty() {
|
||||
continue;
|
||||
}
|
||||
info!(context, "EXPORT: copying filename={}", name);
|
||||
let curr_path_filename = context.get_blobdir().join(entry.file_name());
|
||||
if let Ok(buf) = dc_read_file(context, &curr_path_filename) {
|
||||
if buf.is_empty() {
|
||||
continue;
|
||||
}
|
||||
// bail out if we can't insert
|
||||
stmt.execute(params![name, buf])?;
|
||||
}
|
||||
// bail out if we can't insert
|
||||
stmt.execute(params![name, buf])?;
|
||||
}
|
||||
Ok(true)
|
||||
},
|
||||
)?;
|
||||
ensure!(exported_all_files, "canceled during export-files");
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
* Classic key import
|
||||
******************************************************************************/
|
||||
fn import_self_keys(context: &Context, dir: impl AsRef<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
|
||||
plain ASC keys, at least keys without a password, if we do not want to implement a password entry function.
|
||||
Importing ASC keys is useful to use keys in Delta Chat used by any other non-Autocrypt-PGP implementation.
|
||||
@@ -645,7 +666,7 @@ fn import_self_keys(context: &Context, dir: impl AsRef<Path>) -> Result<()> {
|
||||
match dc_read_file(context, &path_plus_name) {
|
||||
Ok(buf) => {
|
||||
let armored = std::string::String::from_utf8_lossy(&buf);
|
||||
if let Err(err) = set_self_key(context, &armored, set_default, false) {
|
||||
if let Err(err) = set_self_key(context, &armored, set_default, false).await {
|
||||
error!(context, "set_self_key: {}", err);
|
||||
continue;
|
||||
}
|
||||
@@ -662,45 +683,48 @@ fn import_self_keys(context: &Context, dir: impl AsRef<Path>) -> Result<()> {
|
||||
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;
|
||||
|
||||
context.sql.query_map(
|
||||
"SELECT id, public_key, private_key, is_default FROM keypairs;",
|
||||
params![],
|
||||
|row| {
|
||||
let id = row.get(0)?;
|
||||
let public_key_blob: Vec<u8> = row.get(1)?;
|
||||
let public_key = Key::from_slice(&public_key_blob, KeyType::Public);
|
||||
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)?;
|
||||
context
|
||||
.sql
|
||||
.query_map(
|
||||
"SELECT id, public_key, private_key, is_default FROM keypairs;",
|
||||
params![],
|
||||
|row| {
|
||||
let id = row.get(0)?;
|
||||
let public_key_blob: Vec<u8> = row.get(1)?;
|
||||
let public_key = Key::from_slice(&public_key_blob, KeyType::Public);
|
||||
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))
|
||||
},
|
||||
|keys| {
|
||||
for key_pair in keys {
|
||||
let (id, public_key, private_key, is_default) = key_pair?;
|
||||
let id = Some(id).filter(|_| is_default != 0);
|
||||
if let Some(key) = public_key {
|
||||
if export_key_to_asc_file(context, &dir, id, &key).is_err() {
|
||||
Ok((id, public_key, private_key, is_default))
|
||||
},
|
||||
|keys| {
|
||||
for key_pair in keys {
|
||||
let (id, public_key, private_key, is_default) = key_pair?;
|
||||
let id = Some(id).filter(|_| is_default != 0);
|
||||
if let Some(key) = public_key {
|
||||
if export_key_to_asc_file(context, &dir, id, &key).is_err() {
|
||||
export_errors += 1;
|
||||
}
|
||||
} else {
|
||||
export_errors += 1;
|
||||
}
|
||||
} else {
|
||||
export_errors += 1;
|
||||
}
|
||||
if let Some(key) = private_key {
|
||||
if export_key_to_asc_file(context, &dir, id, &key).is_err() {
|
||||
if let Some(key) = private_key {
|
||||
if export_key_to_asc_file(context, &dir, id, &key).is_err() {
|
||||
export_errors += 1;
|
||||
}
|
||||
} else {
|
||||
export_errors += 1;
|
||||
}
|
||||
} else {
|
||||
export_errors += 1;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
},
|
||||
)?;
|
||||
Ok(())
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
|
||||
ensure!(export_errors == 0, "errors while exporting keys");
|
||||
Ok(())
|
||||
@@ -745,7 +769,7 @@ mod tests {
|
||||
let t = test_context(Some(Box::new(logging_cb)));
|
||||
|
||||
configure_alice_keypair(&t.ctx).await;
|
||||
let msg = render_setup_file(&t.ctx, "hello").unwrap();
|
||||
let msg = render_setup_file(&t.ctx, "hello").await.unwrap();
|
||||
println!("{}", &msg);
|
||||
// Check some substrings, indicating things got substituted.
|
||||
// In particular note the mixing of `\r\n` and `\n` depending
|
||||
@@ -767,7 +791,7 @@ mod tests {
|
||||
.set_stock_translation(StockMessage::AcSetupMsgBody, "hello\r\nthere".to_string())
|
||||
.unwrap();
|
||||
configure_alice_keypair(&t.ctx).await;
|
||||
let msg = render_setup_file(&t.ctx, "pw").unwrap();
|
||||
let msg = render_setup_file(&t.ctx, "pw").await.unwrap();
|
||||
println!("{}", &msg);
|
||||
assert!(msg.contains("<p>hello<br>there</p>"));
|
||||
}
|
||||
|
||||
343
src/job.rs
343
src/job.rs
@@ -150,6 +150,7 @@ impl Job {
|
||||
context
|
||||
.sql
|
||||
.execute("DELETE FROM jobs WHERE id=?;", params![self.job_id as i32])
|
||||
.await
|
||||
.is_ok()
|
||||
}
|
||||
|
||||
@@ -157,18 +158,19 @@ impl Job {
|
||||
///
|
||||
/// To add a new job, use [job_add].
|
||||
async fn update(&self, context: &Context) -> bool {
|
||||
sql::execute(
|
||||
context,
|
||||
&context.sql,
|
||||
"UPDATE jobs SET desired_timestamp=?, tries=?, param=? WHERE id=?;",
|
||||
params![
|
||||
self.desired_timestamp,
|
||||
self.tries as i64,
|
||||
self.param.to_string(),
|
||||
self.job_id as i32,
|
||||
],
|
||||
)
|
||||
.is_ok()
|
||||
context
|
||||
.sql
|
||||
.execute(
|
||||
"UPDATE jobs SET desired_timestamp=?, tries=?, param=? WHERE id=?;",
|
||||
params![
|
||||
self.desired_timestamp,
|
||||
self.tries as i64,
|
||||
self.param.to_string(),
|
||||
self.job_id as i32,
|
||||
],
|
||||
)
|
||||
.await
|
||||
.is_ok()
|
||||
}
|
||||
|
||||
async fn smtp_send<F, Fut>(
|
||||
@@ -247,7 +249,7 @@ impl Job {
|
||||
async fn send_msg_to_smtp(&mut self, context: &Context) -> Status {
|
||||
// connect to SMTP server, if not yet done
|
||||
if !context.smtp.is_connected().await {
|
||||
let loginparam = LoginParam::from_database(context, "configured_");
|
||||
let loginparam = LoginParam::from_database(context, "configured_").await;
|
||||
if let Err(err) = context.smtp.connect(context, &loginparam).await {
|
||||
warn!(context, "SMTP connection failure: {:?}", err);
|
||||
return Status::RetryLater;
|
||||
@@ -310,32 +312,35 @@ impl Job {
|
||||
contact_id: u32,
|
||||
) -> sql::Result<(Vec<u32>, Vec<String>)> {
|
||||
// Extract message IDs from job parameters
|
||||
let res: Vec<(u32, MsgId)> = context.sql.query_map(
|
||||
"SELECT id, param FROM jobs WHERE foreign_id=? AND id!=?",
|
||||
params![contact_id, self.job_id],
|
||||
|row| {
|
||||
let job_id: u32 = row.get(0)?;
|
||||
let params_str: String = row.get(1)?;
|
||||
let params: Params = params_str.parse().unwrap_or_default();
|
||||
Ok((job_id, params))
|
||||
},
|
||||
|jobs| {
|
||||
let res = jobs
|
||||
.filter_map(|row| {
|
||||
let (job_id, params) = row.ok()?;
|
||||
let msg_id = params.get_msg_id()?;
|
||||
Some((job_id, msg_id))
|
||||
})
|
||||
.collect();
|
||||
Ok(res)
|
||||
},
|
||||
)?;
|
||||
let res: Vec<(u32, MsgId)> = context
|
||||
.sql
|
||||
.query_map(
|
||||
"SELECT id, param FROM jobs WHERE foreign_id=? AND id!=?",
|
||||
params![contact_id, self.job_id],
|
||||
|row| {
|
||||
let job_id: u32 = row.get(0)?;
|
||||
let params_str: String = row.get(1)?;
|
||||
let params: Params = params_str.parse().unwrap_or_default();
|
||||
Ok((job_id, params))
|
||||
},
|
||||
|jobs| {
|
||||
let res = jobs
|
||||
.filter_map(|row| {
|
||||
let (job_id, params) = row.ok()?;
|
||||
let msg_id = params.get_msg_id()?;
|
||||
Some((job_id, msg_id))
|
||||
})
|
||||
.collect();
|
||||
Ok(res)
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
|
||||
// Load corresponding RFC724 message IDs
|
||||
let mut job_ids = Vec::new();
|
||||
let mut rfc724_mids = Vec::new();
|
||||
for (job_id, msg_id) in res {
|
||||
if let Ok(Message { rfc724_mid, .. }) = Message::load_from_db(context, msg_id) {
|
||||
if let Ok(Message { rfc724_mid, .. }) = Message::load_from_db(context, msg_id).await {
|
||||
job_ids.push(job_id);
|
||||
rfc724_mids.push(rfc724_mid);
|
||||
}
|
||||
@@ -344,14 +349,14 @@ impl Job {
|
||||
}
|
||||
|
||||
async fn send_mdn(&mut self, context: &Context) -> Status {
|
||||
if !context.get_config_bool(Config::MdnsEnabled) {
|
||||
if !context.get_config_bool(Config::MdnsEnabled).await {
|
||||
// User has disabled MDNs after job scheduling but before
|
||||
// execution.
|
||||
return Status::Finished(Err(format_err!("MDNs are disabled")));
|
||||
}
|
||||
|
||||
let contact_id = self.foreign_id;
|
||||
let contact = job_try!(Contact::load_from_db(context, contact_id));
|
||||
let contact = job_try!(Contact::load_from_db(context, contact_id).await);
|
||||
if contact.is_blocked() {
|
||||
return Status::Finished(Err(format_err!("Contact is blocked")));
|
||||
}
|
||||
@@ -379,7 +384,7 @@ impl Job {
|
||||
)
|
||||
}
|
||||
|
||||
let msg = job_try!(Message::load_from_db(context, msg_id));
|
||||
let msg = job_try!(Message::load_from_db(context, msg_id).await);
|
||||
let mimefactory = job_try!(MimeFactory::from_mdn(context, &msg, additional_rfc724_mids));
|
||||
let rendered_msg = job_try!(mimefactory.render());
|
||||
let body = rendered_msg.message;
|
||||
@@ -391,7 +396,7 @@ impl Job {
|
||||
|
||||
// connect to SMTP server, if not yet done
|
||||
if !context.smtp.is_connected().await {
|
||||
let loginparam = LoginParam::from_database(context, "configured_");
|
||||
let loginparam = LoginParam::from_database(context, "configured_").await;
|
||||
if let Err(err) = context.smtp.connect(context, &loginparam).await {
|
||||
warn!(context, "SMTP connection failure: {:?}", err);
|
||||
return Status::RetryLater;
|
||||
@@ -411,7 +416,7 @@ impl Job {
|
||||
async fn move_msg(&mut self, context: &Context) -> Status {
|
||||
let imap_inbox = &context.inbox_thread.imap;
|
||||
|
||||
let msg = job_try!(Message::load_from_db(context, MsgId::new(self.foreign_id)));
|
||||
let msg = job_try!(Message::load_from_db(context, MsgId::new(self.foreign_id)).await);
|
||||
|
||||
if let Err(err) = imap_inbox.ensure_configured_folders(context, true).await {
|
||||
warn!(context, "could not configure folders: {:?}", err);
|
||||
@@ -419,7 +424,8 @@ impl Job {
|
||||
}
|
||||
let dest_folder = context
|
||||
.sql
|
||||
.get_raw_config(context, "configured_mvbox_folder");
|
||||
.get_raw_config(context, "configured_mvbox_folder")
|
||||
.await;
|
||||
|
||||
if let Some(dest_folder) = dest_folder {
|
||||
let server_folder = msg.server_folder.as_ref().unwrap();
|
||||
@@ -453,7 +459,7 @@ impl Job {
|
||||
async fn delete_msg_on_imap(&mut self, context: &Context) -> Status {
|
||||
let imap_inbox = &context.inbox_thread.imap;
|
||||
|
||||
let mut msg = job_try!(Message::load_from_db(context, MsgId::new(self.foreign_id)));
|
||||
let mut msg = job_try!(Message::load_from_db(context, MsgId::new(self.foreign_id)).await);
|
||||
|
||||
if !msg.rfc724_mid.is_empty() {
|
||||
if message::rfc724_mid_cnt(context, &msg.rfc724_mid) > 1 {
|
||||
@@ -488,6 +494,7 @@ impl Job {
|
||||
if let Some(mvbox_folder) = context
|
||||
.sql
|
||||
.get_raw_config(context, "configured_mvbox_folder")
|
||||
.await
|
||||
{
|
||||
imap_inbox.empty_folder(context, &mvbox_folder).await;
|
||||
}
|
||||
@@ -501,7 +508,7 @@ impl Job {
|
||||
async fn markseen_msg_on_imap(&mut self, context: &Context) -> Status {
|
||||
let imap_inbox = &context.inbox_thread.imap;
|
||||
|
||||
let msg = job_try!(Message::load_from_db(context, MsgId::new(self.foreign_id)));
|
||||
let msg = job_try!(Message::load_from_db(context, MsgId::new(self.foreign_id)).await);
|
||||
|
||||
let folder = msg.server_folder.as_ref().unwrap();
|
||||
match imap_inbox.set_seen(context, folder, msg.server_uid).await {
|
||||
@@ -513,7 +520,7 @@ impl Job {
|
||||
// The job will not be retried so locally
|
||||
// there is no risk of double-sending MDNs.
|
||||
if msg.param.get_bool(Param::WantsMdn).unwrap_or_default()
|
||||
&& context.get_config_bool(Config::MdnsEnabled)
|
||||
&& context.get_config_bool(Config::MdnsEnabled).await
|
||||
{
|
||||
if let Err(err) = send_mdn(context, &msg).await {
|
||||
warn!(context, "could not send out mdn for {}: {}", msg.id, err);
|
||||
@@ -543,7 +550,8 @@ impl Job {
|
||||
}
|
||||
let dest_folder = context
|
||||
.sql
|
||||
.get_raw_config(context, "configured_mvbox_folder");
|
||||
.get_raw_config(context, "configured_mvbox_folder")
|
||||
.await;
|
||||
if let Some(dest_folder) = dest_folder {
|
||||
let mut dest_uid = 0;
|
||||
if ImapActionResult::RetryLater
|
||||
@@ -566,67 +574,70 @@ impl Job {
|
||||
|
||||
/// Delete all pending jobs with the given action.
|
||||
pub async fn kill_action(context: &Context, action: Action) -> bool {
|
||||
sql::execute(
|
||||
context,
|
||||
&context.sql,
|
||||
"DELETE FROM jobs WHERE action=?;",
|
||||
params![action],
|
||||
)
|
||||
.is_ok()
|
||||
context
|
||||
.sql
|
||||
.execute("DELETE FROM jobs WHERE action=?;", params![action])
|
||||
.await
|
||||
.is_ok()
|
||||
}
|
||||
|
||||
/// Remove jobs with specified IDs.
|
||||
pub async fn kill_ids(context: &Context, job_ids: &[u32]) -> sql::Result<()> {
|
||||
sql::execute(
|
||||
context,
|
||||
&context.sql,
|
||||
format!(
|
||||
"DELETE FROM jobs WHERE id IN({})",
|
||||
job_ids.iter().map(|_| "?").join(",")
|
||||
),
|
||||
job_ids,
|
||||
)
|
||||
context
|
||||
.sql
|
||||
.execute(
|
||||
format!(
|
||||
"DELETE FROM jobs WHERE id IN({})",
|
||||
job_ids.iter().map(|_| "?").join(",")
|
||||
),
|
||||
job_ids,
|
||||
)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn perform_inbox_fetch(context: &Context) {
|
||||
let use_network = context.get_config_bool(Config::InboxWatch);
|
||||
let use_network = context.get_config_bool(Config::InboxWatch).await;
|
||||
|
||||
context.inbox_thread.fetch(context, use_network).await;
|
||||
}
|
||||
|
||||
pub async fn perform_mvbox_fetch(context: &Context) {
|
||||
let use_network = context.get_config_bool(Config::MvboxWatch);
|
||||
let use_network = context.get_config_bool(Config::MvboxWatch).await;
|
||||
|
||||
context.mvbox_thread.fetch(context, use_network).await;
|
||||
}
|
||||
|
||||
pub async fn perform_sentbox_fetch(context: &Context) {
|
||||
let use_network = context.get_config_bool(Config::SentboxWatch);
|
||||
let use_network = context.get_config_bool(Config::SentboxWatch).await;
|
||||
|
||||
context.sentbox_thread.fetch(context, use_network).await;
|
||||
}
|
||||
|
||||
pub async fn perform_inbox_idle(context: &Context) {
|
||||
if *context.perform_inbox_jobs_needed.clone().read().unwrap() {
|
||||
if context
|
||||
.perform_inbox_jobs_needed
|
||||
.load(std::sync::atomic::Ordering::Relaxed)
|
||||
{
|
||||
info!(
|
||||
context,
|
||||
"INBOX-IDLE will not be started because of waiting jobs."
|
||||
);
|
||||
return;
|
||||
}
|
||||
let use_network = context.get_config_bool(Config::InboxWatch);
|
||||
let use_network = context.get_config_bool(Config::InboxWatch).await;
|
||||
|
||||
context.inbox_thread.idle(context, use_network).await;
|
||||
}
|
||||
|
||||
pub async fn perform_mvbox_idle(context: &Context) {
|
||||
let use_network = context.get_config_bool(Config::MvboxWatch);
|
||||
let use_network = context.get_config_bool(Config::MvboxWatch).await;
|
||||
|
||||
context.mvbox_thread.idle(context, use_network).await;
|
||||
}
|
||||
|
||||
pub async fn perform_sentbox_idle(context: &Context) {
|
||||
let use_network = context.get_config_bool(Config::SentboxWatch);
|
||||
let use_network = context.get_config_bool(Config::SentboxWatch).await;
|
||||
|
||||
context.sentbox_thread.idle(context, use_network).await;
|
||||
}
|
||||
@@ -638,7 +649,9 @@ pub async fn interrupt_inbox_idle(context: &Context) {
|
||||
// If it's currently fetching then we can not get the lock
|
||||
// but we flag it for checking jobs so that idle will be skipped.
|
||||
if !context.inbox_thread.try_interrupt_idle(context).await {
|
||||
*context.perform_inbox_jobs_needed.write().unwrap() = true;
|
||||
context
|
||||
.perform_inbox_jobs_needed
|
||||
.store(true, std::sync::atomic::Ordering::Relaxed);
|
||||
warn!(context, "could not interrupt idle");
|
||||
}
|
||||
}
|
||||
@@ -687,7 +700,7 @@ pub async fn perform_smtp_idle(context: &Context) {
|
||||
);
|
||||
}
|
||||
PerformJobsNeeded::Not | PerformJobsNeeded::AvoidDos => {
|
||||
let dur = get_next_wakeup_time(context, Thread::Smtp);
|
||||
let dur = get_next_wakeup_time(context, Thread::Smtp).await;
|
||||
|
||||
context.smtp.notify_receiver.recv().timeout(dur).await.ok();
|
||||
}
|
||||
@@ -696,7 +709,7 @@ pub async fn perform_smtp_idle(context: &Context) {
|
||||
info!(context, "SMTP-idle ended.",);
|
||||
}
|
||||
|
||||
fn get_next_wakeup_time(context: &Context, thread: Thread) -> time::Duration {
|
||||
async fn get_next_wakeup_time(context: &Context, thread: Thread) -> time::Duration {
|
||||
let t: i64 = context
|
||||
.sql
|
||||
.query_get_value(
|
||||
@@ -704,6 +717,7 @@ fn get_next_wakeup_time(context: &Context, thread: Thread) -> time::Duration {
|
||||
"SELECT MIN(desired_timestamp) FROM jobs WHERE thread=?;",
|
||||
params![thread],
|
||||
)
|
||||
.await
|
||||
.unwrap_or_default();
|
||||
|
||||
let mut wakeup_time = time::Duration::new(10 * 60, 0);
|
||||
@@ -722,7 +736,9 @@ fn get_next_wakeup_time(context: &Context, thread: Thread) -> time::Duration {
|
||||
pub async fn maybe_network(context: &Context) {
|
||||
{
|
||||
context.smtp.state.write().await.probe_network = true;
|
||||
*context.probe_imap_network.write().unwrap() = true;
|
||||
context
|
||||
.probe_imap_network
|
||||
.store(true, std::sync::atomic::Ordering::Relaxed);
|
||||
}
|
||||
|
||||
interrupt_smtp_idle(context).await;
|
||||
@@ -731,14 +747,15 @@ pub async fn maybe_network(context: &Context) {
|
||||
interrupt_sentbox_idle(context).await;
|
||||
}
|
||||
|
||||
pub fn action_exists(context: &Context, action: Action) -> bool {
|
||||
pub async fn action_exists(context: &Context, action: Action) -> bool {
|
||||
context
|
||||
.sql
|
||||
.exists("SELECT id FROM jobs WHERE action=?;", params![action])
|
||||
.await
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
fn set_delivered(context: &Context, msg_id: MsgId) {
|
||||
async fn set_delivered(context: &Context, msg_id: MsgId) {
|
||||
message::update_msg_state(context, msg_id, MessageState::OutDelivered);
|
||||
let chat_id: ChatId = context
|
||||
.sql
|
||||
@@ -747,19 +764,20 @@ fn set_delivered(context: &Context, msg_id: MsgId) {
|
||||
"SELECT chat_id FROM msgs WHERE id=?",
|
||||
params![msg_id],
|
||||
)
|
||||
.await
|
||||
.unwrap_or_default();
|
||||
context.call_cb(Event::MsgDelivered { chat_id, msg_id });
|
||||
}
|
||||
|
||||
// special case for DC_JOB_SEND_MSG_TO_SMTP
|
||||
pub async fn send_msg(context: &Context, msg_id: MsgId) -> Result<()> {
|
||||
let mut msg = Message::load_from_db(context, msg_id)?;
|
||||
let mut msg = Message::load_from_db(context, msg_id).await?;
|
||||
msg.try_calc_and_set_dimensions(context).ok();
|
||||
|
||||
/* create message */
|
||||
let needs_encryption = msg.param.get_bool(Param::GuaranteeE2ee).unwrap_or_default();
|
||||
|
||||
let attach_selfavatar = match chat::shall_attach_selfavatar(context, msg.chat_id) {
|
||||
let attach_selfavatar = match chat::shall_attach_selfavatar(context, msg.chat_id).await {
|
||||
Ok(attach_selfavatar) => attach_selfavatar,
|
||||
Err(err) => {
|
||||
warn!(context, "job: cannot get selfavatar-state: {}", err);
|
||||
@@ -773,9 +791,10 @@ pub async fn send_msg(context: &Context, msg_id: MsgId) -> Result<()> {
|
||||
|
||||
let from = context
|
||||
.get_config(Config::ConfiguredAddr)
|
||||
.await
|
||||
.unwrap_or_default();
|
||||
let lowercase_from = from.to_lowercase();
|
||||
if context.get_config_bool(Config::BccSelf)
|
||||
if context.get_config_bool(Config::BccSelf).await
|
||||
&& !recipients
|
||||
.iter()
|
||||
.any(|x| x.to_lowercase() == lowercase_from)
|
||||
@@ -813,16 +832,17 @@ pub async fn send_msg(context: &Context, msg_id: MsgId) -> Result<()> {
|
||||
}
|
||||
|
||||
if rendered_msg.is_gossiped {
|
||||
chat::set_gossiped_timestamp(context, msg.chat_id, time())?;
|
||||
chat::set_gossiped_timestamp(context, msg.chat_id, time()).await?;
|
||||
}
|
||||
|
||||
if 0 != rendered_msg.last_added_location_id {
|
||||
if let Err(err) = location::set_kml_sent_timestamp(context, msg.chat_id, time()) {
|
||||
if let Err(err) = location::set_kml_sent_timestamp(context, msg.chat_id, time()).await {
|
||||
error!(context, "Failed to set kml sent_timestamp: {:?}", err);
|
||||
}
|
||||
if !msg.hidden {
|
||||
if let Err(err) =
|
||||
location::set_msg_location_id(context, msg.id, rendered_msg.last_added_location_id)
|
||||
.await
|
||||
{
|
||||
error!(context, "Failed to set msg_location_id: {:?}", err);
|
||||
}
|
||||
@@ -830,7 +850,7 @@ pub async fn send_msg(context: &Context, msg_id: MsgId) -> Result<()> {
|
||||
}
|
||||
|
||||
if attach_selfavatar {
|
||||
if let Err(err) = msg.chat_id.set_selfavatar_timestamp(context, time()) {
|
||||
if let Err(err) = msg.chat_id.set_selfavatar_timestamp(context, time()).await {
|
||||
error!(context, "Failed to set selfavatar timestamp: {:?}", err);
|
||||
}
|
||||
}
|
||||
@@ -855,9 +875,15 @@ pub async fn send_msg(context: &Context, msg_id: MsgId) -> Result<()> {
|
||||
pub async fn perform_inbox_jobs(context: &Context) {
|
||||
info!(context, "dc_perform_inbox_jobs starting.",);
|
||||
|
||||
let probe_imap_network = *context.probe_imap_network.clone().read().unwrap();
|
||||
*context.probe_imap_network.write().unwrap() = false;
|
||||
*context.perform_inbox_jobs_needed.write().unwrap() = false;
|
||||
let probe_imap_network = context
|
||||
.probe_imap_network
|
||||
.load(std::sync::atomic::Ordering::Relaxed);
|
||||
context
|
||||
.probe_imap_network
|
||||
.store(false, std::sync::atomic::Ordering::Relaxed);
|
||||
context
|
||||
.perform_inbox_jobs_needed
|
||||
.store(false, std::sync::atomic::Ordering::Relaxed);
|
||||
|
||||
job_perform(context, Thread::Imap, probe_imap_network).await;
|
||||
info!(context, "dc_perform_inbox_jobs ended.",);
|
||||
@@ -872,7 +898,7 @@ pub async fn perform_sentbox_jobs(context: &Context) {
|
||||
}
|
||||
|
||||
async fn job_perform(context: &Context, thread: Thread, probe_network: bool) {
|
||||
while let Some(mut job) = load_next_job(context, thread, probe_network) {
|
||||
while let Some(mut job) = load_next_job(context, thread, probe_network).await {
|
||||
info!(context, "{}-job {} started...", thread, job);
|
||||
|
||||
// some configuration jobs are "exclusive":
|
||||
@@ -891,77 +917,77 @@ async fn job_perform(context: &Context, thread: Thread, probe_network: bool) {
|
||||
x => x,
|
||||
};
|
||||
|
||||
if Action::ConfigureImap == job.action || Action::ImexImap == job.action {
|
||||
context.sentbox_thread.unsuspend(context).await;
|
||||
context.mvbox_thread.unsuspend(context).await;
|
||||
suspend_smtp_thread(context, false).await;
|
||||
break;
|
||||
}
|
||||
// if Action::ConfigureImap == job.action || Action::ImexImap == job.action {
|
||||
// context.sentbox_thread.unsuspend(context).await;
|
||||
// context.mvbox_thread.unsuspend(context).await;
|
||||
// suspend_smtp_thread(context, false).await;
|
||||
// break;
|
||||
// }
|
||||
|
||||
match try_res {
|
||||
Status::RetryNow | Status::RetryLater => {
|
||||
let tries = job.tries + 1;
|
||||
// match try_res {
|
||||
// Status::RetryNow | Status::RetryLater => {
|
||||
// let tries = job.tries + 1;
|
||||
|
||||
if tries < JOB_RETRIES {
|
||||
info!(
|
||||
context,
|
||||
"{} thread increases job {} tries to {}", thread, job, tries
|
||||
);
|
||||
job.tries = tries;
|
||||
let time_offset = get_backoff_time_offset(tries);
|
||||
job.desired_timestamp = time() + time_offset;
|
||||
job.update(context).await;
|
||||
info!(
|
||||
context,
|
||||
"{}-job #{} not succeeded on try #{}, retry in {} seconds.",
|
||||
thread,
|
||||
job.job_id as u32,
|
||||
tries,
|
||||
time_offset
|
||||
);
|
||||
if thread == Thread::Smtp && tries < JOB_RETRIES - 1 {
|
||||
context.smtp.state.write().await.perform_jobs_needed =
|
||||
PerformJobsNeeded::AvoidDos;
|
||||
}
|
||||
} else {
|
||||
info!(
|
||||
context,
|
||||
"{} thread removes job {} as it exhausted {} retries",
|
||||
thread,
|
||||
job,
|
||||
JOB_RETRIES
|
||||
);
|
||||
if job.action == Action::SendMsgToSmtp {
|
||||
message::set_msg_failed(
|
||||
context,
|
||||
MsgId::new(job.foreign_id),
|
||||
job.pending_error.as_ref(),
|
||||
);
|
||||
}
|
||||
job.delete(context).await;
|
||||
}
|
||||
if !probe_network {
|
||||
continue;
|
||||
}
|
||||
// on dc_maybe_network() we stop trying here;
|
||||
// these jobs are already tried once.
|
||||
// otherwise, we just continue with the next job
|
||||
// to give other jobs a chance being tried at least once.
|
||||
break;
|
||||
}
|
||||
Status::Finished(res) => {
|
||||
if let Err(err) = res {
|
||||
warn!(
|
||||
context,
|
||||
"{} removes job {} as it failed with error {:?}", thread, job, err
|
||||
);
|
||||
} else {
|
||||
info!(context, "{} removes job {} as it succeeded", thread, job);
|
||||
}
|
||||
// if tries < JOB_RETRIES {
|
||||
// info!(
|
||||
// context,
|
||||
// "{} thread increases job {} tries to {}", thread, job, tries
|
||||
// );
|
||||
// job.tries = tries;
|
||||
// let time_offset = get_backoff_time_offset(tries);
|
||||
// job.desired_timestamp = time() + time_offset;
|
||||
// job.update(context).await;
|
||||
// info!(
|
||||
// context,
|
||||
// "{}-job #{} not succeeded on try #{}, retry in {} seconds.",
|
||||
// thread,
|
||||
// job.job_id as u32,
|
||||
// tries,
|
||||
// time_offset
|
||||
// );
|
||||
// if thread == Thread::Smtp && tries < JOB_RETRIES - 1 {
|
||||
// context.smtp.state.write().await.perform_jobs_needed =
|
||||
// PerformJobsNeeded::AvoidDos;
|
||||
// }
|
||||
// } else {
|
||||
// info!(
|
||||
// context,
|
||||
// "{} thread removes job {} as it exhausted {} retries",
|
||||
// thread,
|
||||
// job,
|
||||
// JOB_RETRIES
|
||||
// );
|
||||
// if job.action == Action::SendMsgToSmtp {
|
||||
// message::set_msg_failed(
|
||||
// context,
|
||||
// MsgId::new(job.foreign_id),
|
||||
// job.pending_error.as_ref(),
|
||||
// );
|
||||
// }
|
||||
// job.delete(context).await;
|
||||
// }
|
||||
// if !probe_network {
|
||||
// continue;
|
||||
// }
|
||||
// // on dc_maybe_network() we stop trying here;
|
||||
// // these jobs are already tried once.
|
||||
// // otherwise, we just continue with the next job
|
||||
// // to give other jobs a chance being tried at least once.
|
||||
// break;
|
||||
// }
|
||||
// Status::Finished(res) => {
|
||||
// if let Err(err) = res {
|
||||
// warn!(
|
||||
// context,
|
||||
// "{} removes job {} as it failed with error {:?}", thread, job, err
|
||||
// );
|
||||
// } else {
|
||||
// info!(context, "{} removes job {} as it succeeded", thread, job);
|
||||
// }
|
||||
|
||||
job.delete(context).await;
|
||||
}
|
||||
}
|
||||
// job.delete(context).await;
|
||||
// }
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -986,7 +1012,7 @@ async fn perform_job_action(
|
||||
Action::MoveMsg => job.move_msg(context).await,
|
||||
Action::SendMdn => job.send_mdn(context).await,
|
||||
Action::ConfigureImap => job_configure_imap(context).await,
|
||||
Action::ImexImap => match JobImexImap(context, &job) {
|
||||
Action::ImexImap => match job_imex_imap(context, &job).await {
|
||||
Ok(()) => Status::Finished(Ok(())),
|
||||
Err(err) => {
|
||||
error!(context, "{}", err);
|
||||
@@ -994,7 +1020,9 @@ async fn perform_job_action(
|
||||
}
|
||||
},
|
||||
Action::MaybeSendLocations => location::job_maybe_send_locations(context, &job).await,
|
||||
Action::MaybeSendLocationsEnded => location::JobMaybeSendLocationsEnded(context, &mut job),
|
||||
Action::MaybeSendLocationsEnded => {
|
||||
location::job_maybe_send_locations_ended(context, &mut job).await
|
||||
}
|
||||
Action::Housekeeping => {
|
||||
sql::housekeeping(context);
|
||||
Status::Finished(Ok(()))
|
||||
@@ -1078,9 +1106,7 @@ pub async fn add(
|
||||
let timestamp = time();
|
||||
let thread: Thread = action.into();
|
||||
|
||||
sql::execute(
|
||||
context,
|
||||
&context.sql,
|
||||
context.sql.execute(
|
||||
"INSERT INTO jobs (added_timestamp, thread, action, foreign_id, param, desired_timestamp) VALUES (?,?,?,?,?,?);",
|
||||
params![
|
||||
timestamp,
|
||||
@@ -1090,7 +1116,7 @@ pub async fn add(
|
||||
param.to_string(),
|
||||
(timestamp + delay_seconds as i64)
|
||||
]
|
||||
).ok();
|
||||
).await.ok();
|
||||
|
||||
match thread {
|
||||
Thread::Imap => interrupt_inbox_idle(context).await,
|
||||
@@ -1114,7 +1140,7 @@ pub async fn interrupt_smtp_idle(context: &Context) {
|
||||
/// IMAP jobs. The `probe_network` parameter decides how to query
|
||||
/// jobs, this is tricky and probably wrong currently. Look at the
|
||||
/// SQL queries for details.
|
||||
fn load_next_job(context: &Context, thread: Thread, probe_network: bool) -> Option<Job> {
|
||||
async fn load_next_job(context: &Context, thread: Thread, probe_network: bool) -> Option<Job> {
|
||||
let query = if !probe_network {
|
||||
// processing for first-try and after backoff-timeouts:
|
||||
// process jobs in the order they were added.
|
||||
@@ -1165,6 +1191,7 @@ fn load_next_job(context: &Context, thread: Thread, probe_network: bool) -> Opti
|
||||
Ok(None)
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
|
||||
@@ -115,7 +115,7 @@ impl JobThread {
|
||||
let prefix = format!("{}-fetch", self.name);
|
||||
match self.imap.connect_configured(context).await {
|
||||
Ok(()) => {
|
||||
if let Some(watch_folder) = self.get_watch_folder(context) {
|
||||
if let Some(watch_folder) = self.get_watch_folder(context).await {
|
||||
let start = std::time::Instant::now();
|
||||
info!(context, "{} started...", prefix);
|
||||
let res = self
|
||||
@@ -135,8 +135,12 @@ impl JobThread {
|
||||
}
|
||||
}
|
||||
|
||||
fn get_watch_folder(&self, context: &Context) -> Option<String> {
|
||||
match context.sql.get_raw_config(context, self.folder_config_name) {
|
||||
async fn get_watch_folder(&self, context: &Context) -> Option<String> {
|
||||
match context
|
||||
.sql
|
||||
.get_raw_config(context, self.folder_config_name)
|
||||
.await
|
||||
{
|
||||
Some(name) => Some(name),
|
||||
None => {
|
||||
if self.folder_config_name == "configured_inbox_folder" {
|
||||
@@ -184,7 +188,7 @@ impl JobThread {
|
||||
if !self.imap.can_idle().await {
|
||||
true // we have to do fake_idle
|
||||
} else {
|
||||
let watch_folder = self.get_watch_folder(context);
|
||||
let watch_folder = self.get_watch_folder(context).await;
|
||||
info!(context, "{} started...", prefix);
|
||||
let res = self.imap.idle(context, watch_folder).await;
|
||||
info!(context, "{} ended...", prefix);
|
||||
@@ -205,7 +209,7 @@ impl JobThread {
|
||||
}
|
||||
};
|
||||
if do_fake_idle {
|
||||
let watch_folder = self.get_watch_folder(context);
|
||||
let watch_folder = self.get_watch_folder(context).await;
|
||||
self.imap.fake_idle(context, watch_folder).await;
|
||||
}
|
||||
|
||||
|
||||
11
src/key.rs
11
src/key.rs
@@ -197,7 +197,7 @@ impl Key {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_self_public(
|
||||
pub async fn from_self_public(
|
||||
context: &Context,
|
||||
self_addr: impl AsRef<str>,
|
||||
sql: &Sql,
|
||||
@@ -209,10 +209,11 @@ impl Key {
|
||||
"SELECT public_key FROM keypairs WHERE addr=? AND is_default=1;",
|
||||
&[addr],
|
||||
)
|
||||
.await
|
||||
.and_then(|blob: Vec<u8>| Self::from_slice(&blob, KeyType::Public))
|
||||
}
|
||||
|
||||
pub fn from_self_private(
|
||||
pub async fn from_self_private(
|
||||
context: &Context,
|
||||
self_addr: impl AsRef<str>,
|
||||
sql: &Sql,
|
||||
@@ -222,6 +223,7 @@ impl Key {
|
||||
"SELECT private_key FROM keypairs WHERE addr=? AND is_default=1;",
|
||||
&[self_addr.as_ref()],
|
||||
)
|
||||
.await
|
||||
.and_then(|blob: Vec<u8>| Self::from_slice(&blob, KeyType::Private))
|
||||
}
|
||||
|
||||
@@ -347,7 +349,7 @@ impl SaveKeyError {
|
||||
/// same key again overwrites it.
|
||||
///
|
||||
/// [Config::ConfiguredAddr]: crate::config::Config::ConfiguredAddr
|
||||
pub fn store_self_keypair(
|
||||
pub async fn store_self_keypair(
|
||||
context: &Context,
|
||||
keypair: &KeyPair,
|
||||
default: KeyPairUse,
|
||||
@@ -368,11 +370,13 @@ pub fn store_self_keypair(
|
||||
"DELETE FROM keypairs WHERE public_key=? OR private_key=?;",
|
||||
params![public_key, secret_key],
|
||||
)
|
||||
.await
|
||||
.map_err(|err| SaveKeyError::new("failed to remove old use of key", err))?;
|
||||
if default == KeyPairUse::Default {
|
||||
context
|
||||
.sql
|
||||
.execute("UPDATE keypairs SET is_default=0;", params![])
|
||||
.await
|
||||
.map_err(|err| SaveKeyError::new("failed to clear default", err))?;
|
||||
}
|
||||
let is_default = match default {
|
||||
@@ -392,6 +396,7 @@ pub fn store_self_keypair(
|
||||
time()
|
||||
],
|
||||
)
|
||||
.await
|
||||
.map(|_| ())
|
||||
.map_err(|err| SaveKeyError::new("failed to insert keypair", err))
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ impl<'a> Keyring<'a> {
|
||||
&self.keys
|
||||
}
|
||||
|
||||
pub fn load_self_private_for_decrypting(
|
||||
pub async fn load_self_private_for_decrypting(
|
||||
&mut self,
|
||||
context: &Context,
|
||||
self_addr: impl AsRef<str>,
|
||||
@@ -38,6 +38,7 @@ impl<'a> Keyring<'a> {
|
||||
"SELECT private_key FROM keypairs ORDER BY addr=? DESC, is_default DESC;",
|
||||
&[self_addr.as_ref()],
|
||||
)
|
||||
.await
|
||||
.and_then(|blob: Vec<u8>| Key::from_slice(&blob, KeyType::Private))
|
||||
.map(|key| self.add_owned(key))
|
||||
.is_some()
|
||||
|
||||
326
src/location.rs
326
src/location.rs
@@ -15,7 +15,6 @@ use crate::job::{self, Job};
|
||||
use crate::message::{Message, MsgId};
|
||||
use crate::mimeparser::SystemMessage;
|
||||
use crate::param::*;
|
||||
use crate::sql;
|
||||
use crate::stock::StockMessage;
|
||||
|
||||
/// Location record
|
||||
@@ -195,21 +194,22 @@ impl Kml {
|
||||
pub async fn send_locations_to_chat(context: &Context, chat_id: ChatId, seconds: i64) {
|
||||
let now = time();
|
||||
if !(seconds < 0 || chat_id.is_special()) {
|
||||
let is_sending_locations_before = is_sending_locations_to_chat(context, chat_id);
|
||||
if sql::execute(
|
||||
context,
|
||||
&context.sql,
|
||||
"UPDATE chats \
|
||||
let is_sending_locations_before = is_sending_locations_to_chat(context, chat_id).await;
|
||||
if context
|
||||
.sql
|
||||
.execute(
|
||||
"UPDATE chats \
|
||||
SET locations_send_begin=?, \
|
||||
locations_send_until=? \
|
||||
WHERE id=?",
|
||||
params![
|
||||
if 0 != seconds { now } else { 0 },
|
||||
if 0 != seconds { now + seconds } else { 0 },
|
||||
chat_id,
|
||||
],
|
||||
)
|
||||
.is_ok()
|
||||
params![
|
||||
if 0 != seconds { now } else { 0 },
|
||||
if 0 != seconds { now + seconds } else { 0 },
|
||||
chat_id,
|
||||
],
|
||||
)
|
||||
.await
|
||||
.is_ok()
|
||||
{
|
||||
if 0 != seconds && !is_sending_locations_before {
|
||||
let mut msg = Message::new(Viewtype::Text);
|
||||
@@ -241,7 +241,7 @@ pub async fn send_locations_to_chat(context: &Context, chat_id: ChatId, seconds:
|
||||
}
|
||||
|
||||
async fn schedule_maybe_send_locations(context: &Context, force_schedule: bool) {
|
||||
if force_schedule || !job::action_exists(context, job::Action::MaybeSendLocations) {
|
||||
if force_schedule || !job::action_exists(context, job::Action::MaybeSendLocations).await {
|
||||
job::add(
|
||||
context,
|
||||
job::Action::MaybeSendLocations,
|
||||
@@ -253,13 +253,14 @@ async fn schedule_maybe_send_locations(context: &Context, force_schedule: bool)
|
||||
};
|
||||
}
|
||||
|
||||
pub fn is_sending_locations_to_chat(context: &Context, chat_id: ChatId) -> bool {
|
||||
pub async fn is_sending_locations_to_chat(context: &Context, chat_id: ChatId) -> bool {
|
||||
context
|
||||
.sql
|
||||
.exists(
|
||||
"SELECT id FROM chats WHERE (? OR id=?) AND locations_send_until>?;",
|
||||
params![if chat_id.is_unset() { 1 } else { 0 }, chat_id, time()],
|
||||
)
|
||||
.await
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
@@ -269,12 +270,16 @@ pub async fn set(context: &Context, latitude: f64, longitude: f64, accuracy: f64
|
||||
}
|
||||
let mut continue_streaming = false;
|
||||
|
||||
if let Ok(chats) = context.sql.query_map(
|
||||
"SELECT id FROM chats WHERE locations_send_until>?;",
|
||||
params![time()],
|
||||
|row| row.get::<_, i32>(0),
|
||||
|chats| chats.collect::<Result<Vec<_>, _>>().map_err(Into::into),
|
||||
) {
|
||||
if let Ok(chats) = context
|
||||
.sql
|
||||
.query_map(
|
||||
"SELECT id FROM chats WHERE locations_send_until>?;",
|
||||
params![time()],
|
||||
|row| row.get::<_, i32>(0),
|
||||
|chats| chats.collect::<Result<Vec<_>, _>>().map_err(Into::into),
|
||||
)
|
||||
.await
|
||||
{
|
||||
for chat_id in chats {
|
||||
if let Err(err) = context.sql.execute(
|
||||
"INSERT INTO locations \
|
||||
@@ -287,7 +292,7 @@ pub async fn set(context: &Context, latitude: f64, longitude: f64, accuracy: f64
|
||||
chat_id,
|
||||
DC_CONTACT_ID_SELF,
|
||||
]
|
||||
) {
|
||||
).await {
|
||||
warn!(context, "failed to store location {:?}", err);
|
||||
} else {
|
||||
continue_streaming = true;
|
||||
@@ -302,7 +307,7 @@ pub async fn set(context: &Context, latitude: f64, longitude: f64, accuracy: f64
|
||||
continue_streaming
|
||||
}
|
||||
|
||||
pub fn get_range(
|
||||
pub async fn get_range(
|
||||
context: &Context,
|
||||
chat_id: ChatId,
|
||||
contact_id: u32,
|
||||
@@ -360,6 +365,7 @@ pub fn get_range(
|
||||
Ok(ret)
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
@@ -368,17 +374,21 @@ fn is_marker(txt: &str) -> bool {
|
||||
}
|
||||
|
||||
/// Deletes all locations from the database.
|
||||
pub fn delete_all(context: &Context) -> Result<(), Error> {
|
||||
sql::execute(context, &context.sql, "DELETE FROM locations;", params![])?;
|
||||
pub async fn delete_all(context: &Context) -> Result<(), Error> {
|
||||
context
|
||||
.sql
|
||||
.execute("DELETE FROM locations;", params![])
|
||||
.await?;
|
||||
context.call_cb(Event::LocationChanged(None));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn get_kml(context: &Context, chat_id: ChatId) -> Result<(String, u32), Error> {
|
||||
pub async fn get_kml(context: &Context, chat_id: ChatId) -> Result<(String, u32), Error> {
|
||||
let mut last_added_location_id = 0;
|
||||
|
||||
let self_addr = context
|
||||
.get_config(Config::ConfiguredAddr)
|
||||
.await
|
||||
.unwrap_or_default();
|
||||
|
||||
let (locations_send_begin, locations_send_until, locations_last_sent) = context.sql.query_row(
|
||||
@@ -389,7 +399,8 @@ pub fn get_kml(context: &Context, chat_id: ChatId) -> Result<(String, u32), Erro
|
||||
let last_sent: i64 = row.get(2)?;
|
||||
|
||||
Ok((send_begin, send_until, last_sent))
|
||||
})?;
|
||||
})
|
||||
.await?;
|
||||
|
||||
let now = time();
|
||||
let mut location_count = 0;
|
||||
@@ -433,7 +444,7 @@ pub fn get_kml(context: &Context, chat_id: ChatId) -> Result<(String, u32), Erro
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
)?;
|
||||
).await?;
|
||||
ret += "</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,
|
||||
chat_id: ChatId,
|
||||
timestamp: i64,
|
||||
) -> Result<(), Error> {
|
||||
sql::execute(
|
||||
context,
|
||||
&context.sql,
|
||||
"UPDATE chats SET locations_last_sent=? WHERE id=?;",
|
||||
params![timestamp, chat_id],
|
||||
)?;
|
||||
|
||||
context
|
||||
.sql
|
||||
.execute(
|
||||
"UPDATE chats SET locations_last_sent=? WHERE id=?;",
|
||||
params![timestamp, chat_id],
|
||||
)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn set_msg_location_id(
|
||||
pub async fn set_msg_location_id(
|
||||
context: &Context,
|
||||
msg_id: MsgId,
|
||||
location_id: u32,
|
||||
) -> Result<(), Error> {
|
||||
sql::execute(
|
||||
context,
|
||||
&context.sql,
|
||||
"UPDATE msgs SET location_id=? WHERE id=?;",
|
||||
params![location_id, msg_id],
|
||||
)?;
|
||||
context
|
||||
.sql
|
||||
.execute(
|
||||
"UPDATE msgs SET location_id=? WHERE id=?;",
|
||||
params![location_id, msg_id],
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn save(
|
||||
pub async fn save(
|
||||
context: &Context,
|
||||
chat_id: ChatId,
|
||||
contact_id: u32,
|
||||
@@ -504,50 +516,56 @@ pub fn save(
|
||||
independent: bool,
|
||||
) -> Result<u32, Error> {
|
||||
ensure!(!chat_id.is_special(), "Invalid chat id");
|
||||
context
|
||||
|
||||
let newest_location_id = context
|
||||
.sql
|
||||
.prepare2(
|
||||
"SELECT id FROM locations WHERE timestamp=? AND from_id=?",
|
||||
"INSERT INTO locations\
|
||||
.with_conn(|mut conn| {
|
||||
let mut newest_timestamp = 0;
|
||||
let mut newest_location_id = 0;
|
||||
|
||||
for location in locations {
|
||||
let mut stmt_test = conn
|
||||
.prepare_cached("SELECT id FROM locations WHERE timestamp=? AND from_id=?")?;
|
||||
let mut stmt_insert = conn.prepare_cached(
|
||||
"INSERT INTO locations\
|
||||
(timestamp, from_id, chat_id, latitude, longitude, accuracy, independent) \
|
||||
VALUES (?,?,?,?,?,?,?);",
|
||||
|mut stmt_test, mut stmt_insert, conn| {
|
||||
let mut newest_timestamp = 0;
|
||||
let mut newest_location_id = 0;
|
||||
)?;
|
||||
|
||||
for location in locations {
|
||||
let exists =
|
||||
stmt_test.exists(params![location.timestamp, contact_id as i32])?;
|
||||
let exists = stmt_test.exists(params![location.timestamp, contact_id as i32])?;
|
||||
|
||||
if independent || !exists {
|
||||
stmt_insert.execute(params![
|
||||
if independent || !exists {
|
||||
stmt_insert.execute(params![
|
||||
location.timestamp,
|
||||
contact_id as i32,
|
||||
chat_id,
|
||||
location.latitude,
|
||||
location.longitude,
|
||||
location.accuracy,
|
||||
independent,
|
||||
])?;
|
||||
|
||||
if location.timestamp > newest_timestamp {
|
||||
// okay to drop, as we use cached prepared statements
|
||||
drop(stmt_test);
|
||||
drop(stmt_insert);
|
||||
newest_timestamp = location.timestamp;
|
||||
newest_location_id = crate::sql::get_rowid2(
|
||||
context,
|
||||
&mut conn,
|
||||
"locations",
|
||||
"timestamp",
|
||||
location.timestamp,
|
||||
"from_id",
|
||||
contact_id as i32,
|
||||
chat_id,
|
||||
location.latitude,
|
||||
location.longitude,
|
||||
location.accuracy,
|
||||
independent,
|
||||
])?;
|
||||
|
||||
if location.timestamp > newest_timestamp {
|
||||
newest_timestamp = location.timestamp;
|
||||
newest_location_id = sql::get_rowid2_with_conn(
|
||||
context,
|
||||
conn,
|
||||
"locations",
|
||||
"timestamp",
|
||||
location.timestamp,
|
||||
"from_id",
|
||||
contact_id as i32,
|
||||
);
|
||||
}
|
||||
)?;
|
||||
}
|
||||
}
|
||||
Ok(newest_location_id)
|
||||
},
|
||||
)
|
||||
.map_err(Into::into)
|
||||
}
|
||||
Ok(newest_location_id)
|
||||
})
|
||||
.await?;
|
||||
Ok(newest_location_id)
|
||||
}
|
||||
|
||||
pub(crate) async fn job_maybe_send_locations(context: &Context, _job: &Job) -> job::Status {
|
||||
@@ -558,80 +576,87 @@ pub(crate) async fn job_maybe_send_locations(context: &Context, _job: &Job) -> j
|
||||
" ----------------- MAYBE_SEND_LOCATIONS -------------- ",
|
||||
);
|
||||
|
||||
if let Ok(rows) = context.sql.query_map(
|
||||
"SELECT id, locations_send_begin, locations_last_sent \
|
||||
if let Ok(ref rows) = context
|
||||
.sql
|
||||
.query_map(
|
||||
"SELECT id, locations_send_begin, locations_last_sent \
|
||||
FROM chats \
|
||||
WHERE locations_send_until>?;",
|
||||
params![now],
|
||||
|row| {
|
||||
let chat_id: ChatId = row.get(0)?;
|
||||
let locations_send_begin: i64 = row.get(1)?;
|
||||
let locations_last_sent: i64 = row.get(2)?;
|
||||
continue_streaming = true;
|
||||
params![now],
|
||||
|row| {
|
||||
let chat_id: ChatId = row.get(0)?;
|
||||
let locations_send_begin: i64 = row.get(1)?;
|
||||
let locations_last_sent: i64 = row.get(2)?;
|
||||
continue_streaming = true;
|
||||
|
||||
// be a bit tolerant as the timer may not align exactly with time(NULL)
|
||||
if now - locations_last_sent < (60 - 3) {
|
||||
Ok(None)
|
||||
} else {
|
||||
Ok(Some((chat_id, locations_send_begin, locations_last_sent)))
|
||||
}
|
||||
},
|
||||
|rows| {
|
||||
rows.filter_map(|v| v.transpose())
|
||||
.collect::<Result<Vec<_>, _>>()
|
||||
.map_err(Into::into)
|
||||
},
|
||||
) {
|
||||
// be a bit tolerant as the timer may not align exactly with time(NULL)
|
||||
if now - locations_last_sent < (60 - 3) {
|
||||
Ok(None)
|
||||
} else {
|
||||
Ok(Some((chat_id, locations_send_begin, locations_last_sent)))
|
||||
}
|
||||
},
|
||||
|rows| {
|
||||
rows.filter_map(|v| v.transpose())
|
||||
.collect::<Result<Vec<_>, _>>()
|
||||
.map_err(Into::into)
|
||||
},
|
||||
)
|
||||
.await
|
||||
{
|
||||
let msgs = context
|
||||
.sql
|
||||
.prepare(
|
||||
"SELECT id \
|
||||
.with_conn(|conn| {
|
||||
let mut stmt_locations = conn.prepare_cached(
|
||||
"SELECT id \
|
||||
FROM locations \
|
||||
WHERE from_id=? \
|
||||
AND timestamp>=? \
|
||||
AND timestamp>? \
|
||||
AND independent=0 \
|
||||
ORDER BY timestamp;",
|
||||
|mut stmt_locations, _| {
|
||||
let msgs = rows
|
||||
.into_iter()
|
||||
.filter_map(|(chat_id, locations_send_begin, locations_last_sent)| {
|
||||
if !stmt_locations
|
||||
.exists(params![
|
||||
DC_CONTACT_ID_SELF,
|
||||
locations_send_begin,
|
||||
locations_last_sent,
|
||||
])
|
||||
.unwrap_or_default()
|
||||
{
|
||||
// if there is no new location, there's nothing to send.
|
||||
// however, maybe we want to bypass this test eg. 15 minutes
|
||||
None
|
||||
} else {
|
||||
// pending locations are attached automatically to every message,
|
||||
// so also to this empty text message.
|
||||
// DC_CMD_LOCATION is only needed to create a nicer subject.
|
||||
//
|
||||
// for optimisation and to avoid flooding the sending queue,
|
||||
// we could sending these messages only if we're really online.
|
||||
// the easiest way to determine this, is to check for an empty message queue.
|
||||
// (might not be 100%, however, as positions are sent combined later
|
||||
// and dc_set_location() is typically called periodically, this is ok)
|
||||
let mut msg = Message::new(Viewtype::Text);
|
||||
msg.hidden = true;
|
||||
msg.param.set_cmd(SystemMessage::LocationOnly);
|
||||
Some((chat_id, msg))
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
Ok(msgs)
|
||||
},
|
||||
)
|
||||
.unwrap_or_default(); // TODO: Better error handling
|
||||
)?;
|
||||
|
||||
let msgs = rows
|
||||
.iter()
|
||||
.filter_map(|(chat_id, locations_send_begin, locations_last_sent)| {
|
||||
if !stmt_locations
|
||||
.exists(params![
|
||||
DC_CONTACT_ID_SELF,
|
||||
locations_send_begin,
|
||||
locations_last_sent,
|
||||
])
|
||||
.unwrap_or_default()
|
||||
{
|
||||
// if there is no new location, there's nothing to send.
|
||||
// however, maybe we want to bypass this test eg. 15 minutes
|
||||
None
|
||||
} else {
|
||||
// pending locations are attached automatically to every message,
|
||||
// so also to this empty text message.
|
||||
// DC_CMD_LOCATION is only needed to create a nicer subject.
|
||||
//
|
||||
// for optimisation and to avoid flooding the sending queue,
|
||||
// we could sending these messages only if we're really online.
|
||||
// the easiest way to determine this, is to check for an empty message queue.
|
||||
// (might not be 100%, however, as positions are sent combined later
|
||||
// and dc_set_location() is typically called periodically, this is ok)
|
||||
let mut msg = Message::new(Viewtype::Text);
|
||||
msg.hidden = true;
|
||||
msg.param.set_cmd(SystemMessage::LocationOnly);
|
||||
Some((chat_id, msg))
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
Ok(msgs)
|
||||
})
|
||||
.await
|
||||
.unwrap_or_default();
|
||||
|
||||
for (chat_id, mut msg) in msgs.into_iter() {
|
||||
// TODO: better error handling
|
||||
chat::send_msg(context, chat_id, &mut msg)
|
||||
chat::send_msg(context, *chat_id, &mut msg)
|
||||
.await
|
||||
.unwrap_or_default();
|
||||
}
|
||||
@@ -642,19 +667,26 @@ pub(crate) async fn job_maybe_send_locations(context: &Context, _job: &Job) -> j
|
||||
job::Status::Finished(Ok(()))
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
pub(crate) fn JobMaybeSendLocationsEnded(context: &Context, job: &mut Job) -> job::Status {
|
||||
pub(crate) async fn job_maybe_send_locations_ended(
|
||||
context: &Context,
|
||||
job: &mut Job,
|
||||
) -> job::Status {
|
||||
// this function is called when location-streaming _might_ have ended for a chat.
|
||||
// the function checks, if location-streaming is really ended;
|
||||
// if so, a device-message is added if not yet done.
|
||||
|
||||
let chat_id = ChatId::new(job.foreign_id);
|
||||
|
||||
let (send_begin, send_until) = job_try!(context.sql.query_row(
|
||||
"SELECT locations_send_begin, locations_send_until FROM chats WHERE id=?",
|
||||
params![chat_id],
|
||||
|row| Ok((row.get::<_, i64>(0)?, row.get::<_, i64>(1)?)),
|
||||
));
|
||||
let (send_begin, send_until) = job_try!(
|
||||
context
|
||||
.sql
|
||||
.query_row(
|
||||
"SELECT locations_send_begin, locations_send_until FROM chats WHERE id=?",
|
||||
params![chat_id],
|
||||
|row| Ok((row.get::<_, i64>(0)?, row.get::<_, i64>(1)?)),
|
||||
)
|
||||
.await
|
||||
);
|
||||
|
||||
if !(send_begin != 0 && time() <= send_until) {
|
||||
// still streaming -
|
||||
@@ -665,7 +697,7 @@ pub(crate) fn JobMaybeSendLocationsEnded(context: &Context, job: &mut Job) -> jo
|
||||
job_try!(context.sql.execute(
|
||||
"UPDATE chats SET locations_send_begin=0, locations_send_until=0 WHERE id=?",
|
||||
params![chat_id],
|
||||
));
|
||||
).await);
|
||||
|
||||
let stock_str = context.stock_system_msg(StockMessage::MsgLocationDisabled, "", "", 0);
|
||||
chat::add_info_msg(context, chat_id, stock_str);
|
||||
|
||||
@@ -50,59 +50,69 @@ impl LoginParam {
|
||||
}
|
||||
|
||||
/// 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 sql = &context.sql;
|
||||
|
||||
let key = format!("{}addr", prefix);
|
||||
let addr = sql
|
||||
.get_raw_config(context, key)
|
||||
.await
|
||||
.unwrap_or_default()
|
||||
.trim()
|
||||
.to_string();
|
||||
|
||||
let key = format!("{}mail_server", prefix);
|
||||
let mail_server = sql.get_raw_config(context, key).unwrap_or_default();
|
||||
let mail_server = sql.get_raw_config(context, key).await.unwrap_or_default();
|
||||
|
||||
let key = format!("{}mail_port", prefix);
|
||||
let mail_port = sql.get_raw_config_int(context, key).unwrap_or_default();
|
||||
let mail_port = sql
|
||||
.get_raw_config_int(context, key)
|
||||
.await
|
||||
.unwrap_or_default();
|
||||
|
||||
let key = format!("{}mail_user", prefix);
|
||||
let mail_user = sql.get_raw_config(context, key).unwrap_or_default();
|
||||
let mail_user = sql.get_raw_config(context, key).await.unwrap_or_default();
|
||||
|
||||
let key = format!("{}mail_pw", prefix);
|
||||
let mail_pw = sql.get_raw_config(context, key).unwrap_or_default();
|
||||
let mail_pw = sql.get_raw_config(context, key).await.unwrap_or_default();
|
||||
|
||||
let key = format!("{}imap_certificate_checks", prefix);
|
||||
let imap_certificate_checks =
|
||||
if let Some(certificate_checks) = sql.get_raw_config_int(context, key) {
|
||||
if let Some(certificate_checks) = sql.get_raw_config_int(context, key).await {
|
||||
num_traits::FromPrimitive::from_i32(certificate_checks).unwrap()
|
||||
} else {
|
||||
Default::default()
|
||||
};
|
||||
|
||||
let key = format!("{}send_server", prefix);
|
||||
let send_server = sql.get_raw_config(context, key).unwrap_or_default();
|
||||
let send_server = sql.get_raw_config(context, key).await.unwrap_or_default();
|
||||
|
||||
let key = format!("{}send_port", prefix);
|
||||
let send_port = sql.get_raw_config_int(context, key).unwrap_or_default();
|
||||
let send_port = sql
|
||||
.get_raw_config_int(context, key)
|
||||
.await
|
||||
.unwrap_or_default();
|
||||
|
||||
let key = format!("{}send_user", prefix);
|
||||
let send_user = sql.get_raw_config(context, key).unwrap_or_default();
|
||||
let send_user = sql.get_raw_config(context, key).await.unwrap_or_default();
|
||||
|
||||
let key = format!("{}send_pw", prefix);
|
||||
let send_pw = sql.get_raw_config(context, key).unwrap_or_default();
|
||||
let send_pw = sql.get_raw_config(context, key).await.unwrap_or_default();
|
||||
|
||||
let key = format!("{}smtp_certificate_checks", prefix);
|
||||
let smtp_certificate_checks =
|
||||
if let Some(certificate_checks) = sql.get_raw_config_int(context, key) {
|
||||
if let Some(certificate_checks) = sql.get_raw_config_int(context, key).await {
|
||||
num_traits::FromPrimitive::from_i32(certificate_checks).unwrap()
|
||||
} else {
|
||||
Default::default()
|
||||
};
|
||||
|
||||
let key = format!("{}server_flags", prefix);
|
||||
let server_flags = sql.get_raw_config_int(context, key).unwrap_or_default();
|
||||
let server_flags = sql
|
||||
.get_raw_config_int(context, key)
|
||||
.await
|
||||
.unwrap_or_default();
|
||||
|
||||
LoginParam {
|
||||
addr,
|
||||
@@ -125,7 +135,7 @@ impl LoginParam {
|
||||
}
|
||||
|
||||
/// Save this loginparam to the database.
|
||||
pub fn save_to_database(
|
||||
pub async fn save_to_database(
|
||||
&self,
|
||||
context: &Context,
|
||||
prefix: impl AsRef<str>,
|
||||
@@ -134,40 +144,49 @@ impl LoginParam {
|
||||
let sql = &context.sql;
|
||||
|
||||
let key = format!("{}addr", prefix);
|
||||
sql.set_raw_config(context, key, Some(&self.addr))?;
|
||||
sql.set_raw_config(context, key, Some(&self.addr)).await?;
|
||||
|
||||
let key = format!("{}mail_server", prefix);
|
||||
sql.set_raw_config(context, key, Some(&self.mail_server))?;
|
||||
sql.set_raw_config(context, key, Some(&self.mail_server))
|
||||
.await?;
|
||||
|
||||
let key = format!("{}mail_port", prefix);
|
||||
sql.set_raw_config_int(context, key, self.mail_port)?;
|
||||
sql.set_raw_config_int(context, key, self.mail_port).await?;
|
||||
|
||||
let key = format!("{}mail_user", prefix);
|
||||
sql.set_raw_config(context, key, Some(&self.mail_user))?;
|
||||
sql.set_raw_config(context, key, Some(&self.mail_user))
|
||||
.await?;
|
||||
|
||||
let key = format!("{}mail_pw", prefix);
|
||||
sql.set_raw_config(context, key, Some(&self.mail_pw))?;
|
||||
sql.set_raw_config(context, key, Some(&self.mail_pw))
|
||||
.await?;
|
||||
|
||||
let key = format!("{}imap_certificate_checks", prefix);
|
||||
sql.set_raw_config_int(context, key, self.imap_certificate_checks as i32)?;
|
||||
sql.set_raw_config_int(context, key, self.imap_certificate_checks as i32)
|
||||
.await?;
|
||||
|
||||
let key = format!("{}send_server", prefix);
|
||||
sql.set_raw_config(context, key, Some(&self.send_server))?;
|
||||
sql.set_raw_config(context, key, Some(&self.send_server))
|
||||
.await?;
|
||||
|
||||
let key = format!("{}send_port", prefix);
|
||||
sql.set_raw_config_int(context, key, self.send_port)?;
|
||||
sql.set_raw_config_int(context, key, self.send_port).await?;
|
||||
|
||||
let key = format!("{}send_user", prefix);
|
||||
sql.set_raw_config(context, key, Some(&self.send_user))?;
|
||||
sql.set_raw_config(context, key, Some(&self.send_user))
|
||||
.await?;
|
||||
|
||||
let key = format!("{}send_pw", prefix);
|
||||
sql.set_raw_config(context, key, Some(&self.send_pw))?;
|
||||
sql.set_raw_config(context, key, Some(&self.send_pw))
|
||||
.await?;
|
||||
|
||||
let key = format!("{}smtp_certificate_checks", prefix);
|
||||
sql.set_raw_config_int(context, key, self.smtp_certificate_checks as i32)?;
|
||||
sql.set_raw_config_int(context, key, self.smtp_certificate_checks as i32)
|
||||
.await?;
|
||||
|
||||
let key = format!("{}server_flags", prefix);
|
||||
sql.set_raw_config_int(context, key, self.server_flags)?;
|
||||
sql.set_raw_config_int(context, key, self.server_flags)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
201
src/message.rs
201
src/message.rs
@@ -19,7 +19,6 @@ use crate::lot::{Lot, LotState, Meaning};
|
||||
use crate::mimeparser::SystemMessage;
|
||||
use crate::param::*;
|
||||
use crate::pgp::*;
|
||||
use crate::sql;
|
||||
use crate::stock::StockMessage;
|
||||
|
||||
lazy_static! {
|
||||
@@ -219,12 +218,12 @@ impl Message {
|
||||
msg
|
||||
}
|
||||
|
||||
pub fn load_from_db(context: &Context, id: MsgId) -> Result<Message, Error> {
|
||||
pub async fn load_from_db(context: &Context, id: MsgId) -> Result<Message, Error> {
|
||||
ensure!(
|
||||
!id.is_special(),
|
||||
"Can not load special message IDs from DB."
|
||||
);
|
||||
context
|
||||
let msg = context
|
||||
.sql
|
||||
.query_row(
|
||||
concat!(
|
||||
@@ -302,25 +301,23 @@ impl Message {
|
||||
Ok(msg)
|
||||
},
|
||||
)
|
||||
.map_err(Into::into)
|
||||
.await?;
|
||||
|
||||
Ok(msg)
|
||||
}
|
||||
|
||||
pub fn delete_from_db(context: &Context, msg_id: MsgId) {
|
||||
if let Ok(msg) = Message::load_from_db(context, msg_id) {
|
||||
sql::execute(
|
||||
context,
|
||||
&context.sql,
|
||||
"DELETE FROM msgs WHERE id=?;",
|
||||
params![msg.id],
|
||||
)
|
||||
.ok();
|
||||
sql::execute(
|
||||
context,
|
||||
&context.sql,
|
||||
"DELETE FROM msgs_mdns WHERE msg_id=?;",
|
||||
params![msg.id],
|
||||
)
|
||||
.ok();
|
||||
pub async fn delete_from_db(context: &Context, msg_id: MsgId) {
|
||||
if let Ok(msg) = Message::load_from_db(context, msg_id).await {
|
||||
context
|
||||
.sql
|
||||
.execute("DELETE FROM msgs WHERE id=?;", params![msg.id])
|
||||
.await
|
||||
.ok();
|
||||
context
|
||||
.sql
|
||||
.execute("DELETE FROM msgs_mdns WHERE msg_id=?;", params![msg.id])
|
||||
.await
|
||||
.ok();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -477,13 +474,13 @@ impl Message {
|
||||
self.param.get_int(Param::GuaranteeE2ee).unwrap_or_default() != 0
|
||||
}
|
||||
|
||||
pub fn get_summary(&mut self, context: &Context, chat: Option<&Chat>) -> Lot {
|
||||
pub async fn get_summary(&mut self, context: &Context, chat: Option<&Chat>) -> Lot {
|
||||
let mut ret = Lot::new();
|
||||
|
||||
let chat_loaded: Chat;
|
||||
let chat = if let Some(chat) = chat {
|
||||
chat
|
||||
} else if let Ok(chat) = Chat::load_from_db(context, self.chat_id) {
|
||||
} else if let Ok(chat) = Chat::load_from_db(context, self.chat_id).await {
|
||||
chat_loaded = chat;
|
||||
&chat_loaded
|
||||
} else {
|
||||
@@ -493,7 +490,7 @@ impl Message {
|
||||
let contact = if self.from_id != DC_CONTACT_ID_SELF as u32
|
||||
&& (chat.typ == Chattype::Group || chat.typ == Chattype::VerifiedGroup)
|
||||
{
|
||||
Contact::get_by_id(context, self.from_id).ok()
|
||||
Contact::get_by_id(context, self.from_id).await.ok()
|
||||
} else {
|
||||
None
|
||||
};
|
||||
@@ -615,14 +612,15 @@ impl Message {
|
||||
self.save_param_to_disk(context);
|
||||
}
|
||||
|
||||
pub fn save_param_to_disk(&mut self, context: &Context) -> bool {
|
||||
sql::execute(
|
||||
context,
|
||||
&context.sql,
|
||||
"UPDATE msgs SET param=? WHERE id=?;",
|
||||
params![self.param.to_string(), self.id],
|
||||
)
|
||||
.is_ok()
|
||||
pub async fn save_param_to_disk(&mut self, context: &Context) -> bool {
|
||||
context
|
||||
.sql
|
||||
.execute(
|
||||
"UPDATE msgs SET param=? WHERE id=?;",
|
||||
params![self.param.to_string(), self.id],
|
||||
)
|
||||
.await
|
||||
.is_ok()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -793,21 +791,24 @@ impl Lot {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_msg_info(context: &Context, msg_id: MsgId) -> String {
|
||||
pub async fn get_msg_info(context: &Context, msg_id: MsgId) -> String {
|
||||
let mut ret = String::new();
|
||||
|
||||
let msg = Message::load_from_db(context, msg_id);
|
||||
let msg = Message::load_from_db(context, msg_id).await;
|
||||
if msg.is_err() {
|
||||
return ret;
|
||||
}
|
||||
|
||||
let msg = msg.unwrap_or_default();
|
||||
|
||||
let rawtxt: Option<String> = context.sql.query_get_value(
|
||||
context,
|
||||
"SELECT txt_raw FROM msgs WHERE id=?;",
|
||||
params![msg_id],
|
||||
);
|
||||
let rawtxt: Option<String> = context
|
||||
.sql
|
||||
.query_get_value(
|
||||
context,
|
||||
"SELECT txt_raw FROM msgs WHERE id=?;",
|
||||
params![msg_id],
|
||||
)
|
||||
.await;
|
||||
|
||||
if rawtxt.is_none() {
|
||||
ret += &format!("Cannot load message {}.", msg_id);
|
||||
@@ -820,6 +821,7 @@ pub fn get_msg_info(context: &Context, msg_id: MsgId) -> String {
|
||||
ret += &format!("Sent: {}", fts);
|
||||
|
||||
let name = Contact::load_from_db(context, msg.from_id)
|
||||
.await
|
||||
.map(|contact| contact.get_name_n_addr())
|
||||
.unwrap_or_default();
|
||||
|
||||
@@ -952,7 +954,7 @@ pub fn get_mime_headers(context: &Context, msg_id: MsgId) -> Option<String> {
|
||||
|
||||
pub async fn delete_msgs(context: &Context, msg_ids: &[MsgId]) {
|
||||
for msg_id in msg_ids.iter() {
|
||||
if let Ok(msg) = Message::load_from_db(context, *msg_id) {
|
||||
if let Ok(msg) = Message::load_from_db(context, *msg_id).await {
|
||||
if msg.location_id > 0 {
|
||||
delete_poi_location(context, msg.location_id);
|
||||
}
|
||||
@@ -978,24 +980,26 @@ pub async fn delete_msgs(context: &Context, msg_ids: &[MsgId]) {
|
||||
}
|
||||
}
|
||||
|
||||
fn update_msg_chat_id(context: &Context, msg_id: MsgId, chat_id: ChatId) -> bool {
|
||||
sql::execute(
|
||||
context,
|
||||
&context.sql,
|
||||
"UPDATE msgs SET chat_id=? WHERE id=?;",
|
||||
params![chat_id, msg_id],
|
||||
)
|
||||
.is_ok()
|
||||
async fn update_msg_chat_id(context: &Context, msg_id: MsgId, chat_id: ChatId) -> bool {
|
||||
context
|
||||
.sql
|
||||
.execute(
|
||||
"UPDATE msgs SET chat_id=? WHERE id=?;",
|
||||
params![chat_id, msg_id],
|
||||
)
|
||||
.await
|
||||
.is_ok()
|
||||
}
|
||||
|
||||
fn delete_poi_location(context: &Context, location_id: u32) -> bool {
|
||||
sql::execute(
|
||||
context,
|
||||
&context.sql,
|
||||
"DELETE FROM locations WHERE independent = 1 AND id=?;",
|
||||
params![location_id as i32],
|
||||
)
|
||||
.is_ok()
|
||||
async fn delete_poi_location(context: &Context, location_id: u32) -> bool {
|
||||
context
|
||||
.sql
|
||||
.execute(
|
||||
"DELETE FROM locations WHERE independent = 1 AND id=?;",
|
||||
params![location_id as i32],
|
||||
)
|
||||
.await
|
||||
.is_ok()
|
||||
}
|
||||
|
||||
pub async fn markseen_msgs(context: &Context, msg_ids: &[MsgId]) -> bool {
|
||||
@@ -1003,16 +1007,18 @@ pub async fn markseen_msgs(context: &Context, msg_ids: &[MsgId]) -> bool {
|
||||
return false;
|
||||
}
|
||||
|
||||
let msgs = context.sql.prepare(
|
||||
concat!(
|
||||
"SELECT",
|
||||
" m.state AS state,",
|
||||
" c.blocked AS blocked",
|
||||
" FROM msgs m LEFT JOIN chats c ON c.id=m.chat_id",
|
||||
" WHERE m.id=? AND m.chat_id>9"
|
||||
),
|
||||
|mut stmt, _| {
|
||||
let mut res = Vec::with_capacity(msg_ids.len());
|
||||
let msgs = context
|
||||
.sql
|
||||
.with_conn(|conn| {
|
||||
let mut stmt = conn.prepare_cached(concat!(
|
||||
"SELECT",
|
||||
" m.state AS state,",
|
||||
" c.blocked AS blocked",
|
||||
" FROM msgs m LEFT JOIN chats c ON c.id=m.chat_id",
|
||||
" WHERE m.id=? AND m.chat_id>9"
|
||||
))?;
|
||||
|
||||
let mut msgs = Vec::with_capacity(msg_ids.len());
|
||||
for id in msg_ids.iter() {
|
||||
let query_res = stmt.query_row(params![*id], |row| {
|
||||
Ok((
|
||||
@@ -1024,20 +1030,18 @@ pub async fn markseen_msgs(context: &Context, msg_ids: &[MsgId]) -> bool {
|
||||
if let Err(rusqlite::Error::QueryReturnedNoRows) = query_res {
|
||||
continue;
|
||||
}
|
||||
let (state, blocked) = query_res?;
|
||||
res.push((id, state, blocked));
|
||||
let (state, blocked) = query_res
|
||||
.map_err(|err| Error::SqlError(err.into()))
|
||||
.expect("query fail");
|
||||
msgs.push((id, state, blocked));
|
||||
}
|
||||
|
||||
Ok(res)
|
||||
},
|
||||
);
|
||||
Ok(msgs)
|
||||
})
|
||||
.await
|
||||
.unwrap_or_default();
|
||||
|
||||
if msgs.is_err() {
|
||||
warn!(context, "markseen_msgs failed: {:?}", msgs);
|
||||
return false;
|
||||
}
|
||||
let mut send_event = false;
|
||||
let msgs = msgs.unwrap_or_default();
|
||||
|
||||
for (id, curr_state, curr_blocked) in msgs.into_iter() {
|
||||
if curr_blocked == Blocked::Not {
|
||||
@@ -1071,14 +1075,15 @@ pub async fn markseen_msgs(context: &Context, msg_ids: &[MsgId]) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
pub fn update_msg_state(context: &Context, msg_id: MsgId, state: MessageState) -> bool {
|
||||
sql::execute(
|
||||
context,
|
||||
&context.sql,
|
||||
"UPDATE msgs SET state=? WHERE id=?;",
|
||||
params![state, msg_id],
|
||||
)
|
||||
.is_ok()
|
||||
pub async fn update_msg_state(context: &Context, msg_id: MsgId, state: MessageState) -> bool {
|
||||
context
|
||||
.sql
|
||||
.execute(
|
||||
"UPDATE msgs SET state=? WHERE id=?;",
|
||||
params![state, msg_id],
|
||||
)
|
||||
.await
|
||||
.is_ok()
|
||||
}
|
||||
|
||||
pub fn star_msgs(context: &Context, msg_ids: &[MsgId], star: bool) -> bool {
|
||||
@@ -1189,8 +1194,8 @@ pub fn exists(context: &Context, msg_id: MsgId) -> bool {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_msg_failed(context: &Context, msg_id: MsgId, error: Option<impl AsRef<str>>) {
|
||||
if let Ok(mut msg) = Message::load_from_db(context, msg_id) {
|
||||
pub async fn set_msg_failed(context: &Context, msg_id: MsgId, error: Option<impl AsRef<str>>) {
|
||||
if let Ok(mut msg) = Message::load_from_db(context, msg_id).await {
|
||||
if msg.state.can_fail() {
|
||||
msg.state = MessageState::OutFailed;
|
||||
}
|
||||
@@ -1199,13 +1204,14 @@ pub fn set_msg_failed(context: &Context, msg_id: MsgId, error: Option<impl AsRef
|
||||
error!(context, "{}", error.as_ref());
|
||||
}
|
||||
|
||||
if sql::execute(
|
||||
context,
|
||||
&context.sql,
|
||||
"UPDATE msgs SET state=?, param=? WHERE id=?;",
|
||||
params![msg.state, msg.param.to_string(), msg_id],
|
||||
)
|
||||
.is_ok()
|
||||
if context
|
||||
.sql
|
||||
.execute(
|
||||
"UPDATE msgs SET state=?, param=? WHERE id=?;",
|
||||
params![msg.state, msg.param.to_string(), msg_id],
|
||||
)
|
||||
.await
|
||||
.is_ok()
|
||||
{
|
||||
context.call_cb(Event::MsgFailed {
|
||||
chat_id: msg.chat_id,
|
||||
@@ -1429,21 +1435,22 @@ mod tests {
|
||||
let d = test::dummy_context();
|
||||
let ctx = &d.ctx;
|
||||
|
||||
let contact =
|
||||
Contact::create(ctx, "", "dest@example.com").expect("failed to create contact");
|
||||
let contact = Contact::create(ctx, "", "dest@example.com")
|
||||
.await
|
||||
.expect("failed to create contact");
|
||||
|
||||
let res = ctx
|
||||
.set_config(Config::ConfiguredAddr, Some("self@example.com"))
|
||||
.await;
|
||||
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 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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
.context
|
||||
.get_config(Config::ConfiguredAddr)
|
||||
.await
|
||||
.ok_or_else(|| format_err!("Not configured"))?;
|
||||
|
||||
Ok(self
|
||||
|
||||
@@ -80,7 +80,7 @@ impl Default for SystemMessage {
|
||||
const MIME_AC_SETUP_FILE: &str = "application/autocrypt-setup";
|
||||
|
||||
impl MimeMessage {
|
||||
pub fn from_bytes(context: &Context, body: &[u8]) -> Result<Self> {
|
||||
pub async fn from_bytes(context: &Context, body: &[u8]) -> Result<Self> {
|
||||
let mail = mailparse::parse_mail(body)?;
|
||||
|
||||
let message_time = mail
|
||||
@@ -98,7 +98,7 @@ impl MimeMessage {
|
||||
let mail_raw;
|
||||
let mut gossipped_addr = Default::default();
|
||||
|
||||
let (mail, signatures) = match e2ee::try_decrypt(context, &mail, message_time) {
|
||||
let (mail, signatures) = match e2ee::try_decrypt(context, &mail, message_time).await {
|
||||
Ok((raw, signatures)) => {
|
||||
if let Some(raw) = raw {
|
||||
// Valid autocrypt message, encrypted
|
||||
@@ -114,7 +114,8 @@ impl MimeMessage {
|
||||
let gossip_headers =
|
||||
decrypted_mail.headers.get_all_values("Autocrypt-Gossip")?;
|
||||
gossipped_addr =
|
||||
update_gossip_peerstates(context, message_time, &mail, gossip_headers)?;
|
||||
update_gossip_peerstates(context, message_time, &mail, gossip_headers)
|
||||
.await?;
|
||||
|
||||
// let known protected headers from the decrypted
|
||||
// part override the unencrypted top-level
|
||||
@@ -837,7 +838,7 @@ impl MimeMessage {
|
||||
let mut param = Params::new();
|
||||
param.set(Param::ServerFolder, server_folder.as_ref());
|
||||
param.set_int(Param::ServerUid, server_uid as i32);
|
||||
if self.has_chat_version() && context.get_config_bool(Config::MvboxMove) {
|
||||
if self.has_chat_version() && context.get_config_bool(Config::MvboxMove).await {
|
||||
param.set_int(Param::AlsoMove, 1);
|
||||
}
|
||||
job::add(context, Action::MarkseenMdnOnImap, 0, param, 0).await;
|
||||
@@ -845,7 +846,7 @@ impl MimeMessage {
|
||||
}
|
||||
}
|
||||
|
||||
fn update_gossip_peerstates(
|
||||
async fn update_gossip_peerstates(
|
||||
context: &Context,
|
||||
message_time: i64,
|
||||
mail: &mailparse::ParsedMail<'_>,
|
||||
@@ -878,15 +879,15 @@ fn update_gossip_peerstates(
|
||||
let mut peerstate = Peerstate::from_addr(context, &context.sql, &header.addr);
|
||||
if let Some(ref mut peerstate) = peerstate {
|
||||
peerstate.apply_gossip(header, message_time);
|
||||
peerstate.save_to_db(&context.sql, false)?;
|
||||
peerstate.save_to_db(&context.sql, false).await?;
|
||||
} else {
|
||||
let p = Peerstate::from_gossip(context, header, message_time);
|
||||
p.save_to_db(&context.sql, true)?;
|
||||
p.save_to_db(&context.sql, true).await?;
|
||||
peerstate = Some(p);
|
||||
}
|
||||
if let Some(peerstate) = peerstate {
|
||||
if peerstate.degrade_event.is_some() {
|
||||
handle_degrade_event(context, &peerstate)?;
|
||||
handle_degrade_event(context, &peerstate).await?;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ use crate::aheader::*;
|
||||
use crate::constants::*;
|
||||
use crate::context::Context;
|
||||
use crate::key::{Key, SignedPublicKey};
|
||||
use crate::sql::{self, Sql};
|
||||
use crate::sql::Sql;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum PeerstateKeyType {
|
||||
@@ -409,20 +409,17 @@ impl<'a> Peerstate<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn save_to_db(&self, sql: &Sql, create: bool) -> crate::sql::Result<()> {
|
||||
pub async fn save_to_db(&self, sql: &Sql, create: bool) -> crate::sql::Result<()> {
|
||||
if create {
|
||||
sql::execute(
|
||||
self.context,
|
||||
sql,
|
||||
sql.execute(
|
||||
"INSERT INTO acpeerstates (addr) VALUES(?);",
|
||||
params![self.addr],
|
||||
)?;
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
|
||||
if self.to_save == Some(ToSave::All) || create {
|
||||
sql::execute(
|
||||
self.context,
|
||||
sql,
|
||||
sql.execute(
|
||||
"UPDATE acpeerstates \
|
||||
SET last_seen=?, last_seen_autocrypt=?, prefer_encrypted=?, \
|
||||
public_key=?, gossip_timestamp=?, gossip_key=?, public_key_fingerprint=?, gossip_key_fingerprint=?, \
|
||||
@@ -441,11 +438,9 @@ impl<'a> Peerstate<'a> {
|
||||
&self.verified_key_fingerprint,
|
||||
&self.addr,
|
||||
],
|
||||
)?;
|
||||
).await?;
|
||||
} else if self.to_save == Some(ToSave::Timestamps) {
|
||||
sql::execute(
|
||||
self.context,
|
||||
sql,
|
||||
sql.execute(
|
||||
"UPDATE acpeerstates SET last_seen=?, last_seen_autocrypt=?, gossip_timestamp=? \
|
||||
WHERE addr=?;",
|
||||
params![
|
||||
@@ -454,7 +449,8 @@ impl<'a> Peerstate<'a> {
|
||||
self.gossip_timestamp,
|
||||
&self.addr
|
||||
],
|
||||
)?;
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
||||
@@ -40,13 +40,13 @@ impl Into<Lot> for Error {
|
||||
/// Check a scanned QR code.
|
||||
/// The function should be called after a QR code is scanned.
|
||||
/// The function takes the raw text scanned and checks what can be done with it.
|
||||
pub fn check_qr(context: &Context, qr: impl AsRef<str>) -> Lot {
|
||||
pub async fn check_qr(context: &Context, qr: impl AsRef<str>) -> Lot {
|
||||
let qr = qr.as_ref();
|
||||
|
||||
info!(context, "Scanned QR code: {}", qr);
|
||||
|
||||
if qr.starts_with(OPENPGP4FPR_SCHEME) {
|
||||
decode_openpgp(context, qr)
|
||||
decode_openpgp(context, qr).await
|
||||
} else if qr.starts_with(DCACCOUNT_SCHEME) {
|
||||
decode_account(context, qr)
|
||||
} else if qr.starts_with(MAILTO_SCHEME) {
|
||||
@@ -66,7 +66,7 @@ pub fn check_qr(context: &Context, qr: impl AsRef<str>) -> Lot {
|
||||
|
||||
/// scheme: `OPENPGP4FPR:FINGERPRINT#a=ADDR&n=NAME&i=INVITENUMBER&s=AUTH`
|
||||
/// or: `OPENPGP4FPR:FINGERPRINT#a=ADDR&g=GROUPNAME&x=GROUPID&i=INVITENUMBER&s=AUTH`
|
||||
fn decode_openpgp(context: &Context, qr: &str) -> Lot {
|
||||
async fn decode_openpgp(context: &Context, qr: &str) -> Lot {
|
||||
let payload = &qr[OPENPGP4FPR_SCHEME.len()..];
|
||||
|
||||
let (fingerprint, fragment) = match payload.find('#').map(|offset| {
|
||||
@@ -152,10 +152,12 @@ fn decode_openpgp(context: &Context, qr: &str) -> Lot {
|
||||
peerstate.addr.clone(),
|
||||
Origin::UnhandledQrScan,
|
||||
)
|
||||
.await
|
||||
.map(|(id, _)| id)
|
||||
.unwrap_or_default();
|
||||
|
||||
let (id, _) = chat::create_or_lookup_by_contact_id(context, lot.id, Blocked::Deaddrop)
|
||||
.await
|
||||
.unwrap_or_default();
|
||||
|
||||
chat::add_info_msg(context, id, format!("{} verified.", peerstate.addr));
|
||||
@@ -172,6 +174,7 @@ fn decode_openpgp(context: &Context, qr: &str) -> Lot {
|
||||
lot.state = LotState::QrAskVerifyContact;
|
||||
}
|
||||
lot.id = Contact::add_or_lookup(context, &name, &addr, Origin::UnhandledQrScan)
|
||||
.await
|
||||
.map(|(id, _)| id)
|
||||
.unwrap_or_default();
|
||||
|
||||
|
||||
@@ -143,35 +143,40 @@ fn get_self_fingerprint(context: &Context) -> Option<String> {
|
||||
None
|
||||
}
|
||||
|
||||
async fn cleanup(
|
||||
context: &Context,
|
||||
contact_chat_id: ChatId,
|
||||
ongoing_allocated: bool,
|
||||
join_vg: bool,
|
||||
) -> ChatId {
|
||||
let mut bob = context.bob.write().unwrap();
|
||||
bob.expects = 0;
|
||||
let ret_chat_id: ChatId = if bob.status == DC_BOB_SUCCESS {
|
||||
if join_vg {
|
||||
chat::get_chat_id_by_grpid(
|
||||
context,
|
||||
bob.qr_scan.as_ref().unwrap().text2.as_ref().unwrap(),
|
||||
)
|
||||
.await
|
||||
.unwrap_or((ChatId::new(0), false, Blocked::Not))
|
||||
.0
|
||||
} else {
|
||||
contact_chat_id
|
||||
}
|
||||
} else {
|
||||
ChatId::new(0)
|
||||
};
|
||||
bob.qr_scan = None;
|
||||
|
||||
if ongoing_allocated {
|
||||
context.free_ongoing();
|
||||
}
|
||||
ret_chat_id
|
||||
}
|
||||
|
||||
/// Take a scanned QR-code and do the setup-contact/join-group handshake.
|
||||
/// See the ffi-documentation for more details.
|
||||
pub async fn dc_join_securejoin(context: &Context, qr: &str) -> ChatId {
|
||||
let cleanup =
|
||||
|context: &Context, contact_chat_id: ChatId, ongoing_allocated: bool, join_vg: bool| {
|
||||
let mut bob = context.bob.write().unwrap();
|
||||
bob.expects = 0;
|
||||
let ret_chat_id: ChatId = if bob.status == DC_BOB_SUCCESS {
|
||||
if join_vg {
|
||||
chat::get_chat_id_by_grpid(
|
||||
context,
|
||||
bob.qr_scan.as_ref().unwrap().text2.as_ref().unwrap(),
|
||||
)
|
||||
.unwrap_or((ChatId::new(0), false, Blocked::Not))
|
||||
.0
|
||||
} else {
|
||||
contact_chat_id
|
||||
}
|
||||
} else {
|
||||
ChatId::new(0)
|
||||
};
|
||||
bob.qr_scan = None;
|
||||
|
||||
if ongoing_allocated {
|
||||
context.free_ongoing();
|
||||
}
|
||||
ret_chat_id
|
||||
};
|
||||
|
||||
/*========================================================
|
||||
==== Bob - the joiner's side =====
|
||||
==== Step 2 in "Setup verified contact" protocol =====
|
||||
@@ -181,25 +186,25 @@ pub async fn dc_join_securejoin(context: &Context, qr: &str) -> ChatId {
|
||||
let mut join_vg: bool = false;
|
||||
|
||||
info!(context, "Requesting secure-join ...",);
|
||||
ensure_secret_key_exists(context).ok();
|
||||
ensure_secret_key_exists(context).await.ok();
|
||||
if !context.alloc_ongoing() {
|
||||
return cleanup(&context, contact_chat_id, false, join_vg);
|
||||
return cleanup(&context, contact_chat_id, false, join_vg).await;
|
||||
}
|
||||
let qr_scan = check_qr(context, &qr);
|
||||
let qr_scan = check_qr(context, &qr).await;
|
||||
if qr_scan.state != LotState::QrAskVerifyContact && qr_scan.state != LotState::QrAskVerifyGroup
|
||||
{
|
||||
error!(context, "Unknown QR code.",);
|
||||
return cleanup(&context, contact_chat_id, true, join_vg);
|
||||
return cleanup(&context, contact_chat_id, true, join_vg).await;
|
||||
}
|
||||
contact_chat_id = match chat::create_by_contact_id(context, qr_scan.id) {
|
||||
contact_chat_id = match chat::create_by_contact_id(context, qr_scan.id).await {
|
||||
Ok(chat_id) => chat_id,
|
||||
Err(_) => {
|
||||
error!(context, "Unknown contact.");
|
||||
return cleanup(&context, contact_chat_id, true, join_vg);
|
||||
return cleanup(&context, contact_chat_id, true, join_vg).await;
|
||||
}
|
||||
};
|
||||
if context.shall_stop_ongoing() {
|
||||
return cleanup(&context, contact_chat_id, true, join_vg);
|
||||
return cleanup(&context, contact_chat_id, true, join_vg).await;
|
||||
}
|
||||
join_vg = qr_scan.get_state() == LotState::QrAskVerifyGroup;
|
||||
{
|
||||
@@ -266,7 +271,7 @@ pub async fn dc_join_securejoin(context: &Context, qr: &str) -> ChatId {
|
||||
while !context.shall_stop_ongoing() {
|
||||
std::thread::sleep(std::time::Duration::from_millis(200));
|
||||
}
|
||||
cleanup(&context, contact_chat_id, true, join_vg)
|
||||
cleanup(&context, contact_chat_id, true, join_vg).await
|
||||
} else {
|
||||
// for a one-to-one-chat, the chat is already known, return the chat-id,
|
||||
// the verification runs in background
|
||||
@@ -412,7 +417,7 @@ pub(crate) async fn handle_securejoin_handshake(
|
||||
);
|
||||
|
||||
let contact_chat_id =
|
||||
match chat::create_or_lookup_by_contact_id(context, contact_id, Blocked::Not) {
|
||||
match chat::create_or_lookup_by_contact_id(context, contact_id, Blocked::Not).await {
|
||||
Ok((chat_id, blocked)) => {
|
||||
if blocked != Blocked::Not {
|
||||
chat_id.unblock(context);
|
||||
@@ -447,7 +452,7 @@ pub(crate) async fn handle_securejoin_handshake(
|
||||
return Ok(HandshakeMessage::Ignore);
|
||||
}
|
||||
};
|
||||
if !token::exists(context, token::Namespace::InviteNumber, &invitenumber) {
|
||||
if !token::exists(context, token::Namespace::InviteNumber, &invitenumber).await {
|
||||
warn!(context, "Secure-join denied (bad invitenumber).");
|
||||
return Ok(HandshakeMessage::Ignore);
|
||||
}
|
||||
@@ -583,7 +588,7 @@ pub(crate) async fn handle_securejoin_handshake(
|
||||
return Ok(HandshakeMessage::Ignore);
|
||||
}
|
||||
};
|
||||
if !token::exists(context, token::Namespace::Auth, &auth_0) {
|
||||
if !token::exists(context, token::Namespace::Auth, &auth_0).await {
|
||||
could_not_establish_secure_connection(context, contact_chat_id, "Auth invalid.");
|
||||
return Ok(HandshakeMessage::Ignore);
|
||||
}
|
||||
@@ -611,7 +616,7 @@ pub(crate) async fn handle_securejoin_handshake(
|
||||
return Ok(HandshakeMessage::Ignore);
|
||||
}
|
||||
};
|
||||
match chat::get_chat_id_by_grpid(context, field_grpid) {
|
||||
match chat::get_chat_id_by_grpid(context, field_grpid).await {
|
||||
Ok((group_chat_id, _, _)) => {
|
||||
if let Err(err) =
|
||||
chat::add_contact_to_chat_ex(context, group_chat_id, contact_id, true)
|
||||
@@ -672,6 +677,7 @@ pub(crate) async fn handle_securejoin_handshake(
|
||||
// only after we have returned. It does not impact
|
||||
// the security invariants of secure-join however.
|
||||
let (_, is_verified_group, _) = chat::get_chat_id_by_grpid(context, &group_id)
|
||||
.await
|
||||
.unwrap_or((ChatId::new(0), false, Blocked::Not));
|
||||
// when joining a non-verified group
|
||||
// the vg-member-added message may be unencrypted
|
||||
@@ -711,6 +717,7 @@ pub(crate) async fn handle_securejoin_handshake(
|
||||
if join_vg
|
||||
&& !context
|
||||
.is_self_addr(cg_member_added)
|
||||
.await
|
||||
.map_err(|_| HandshakeError::NoSelfAddr)?
|
||||
{
|
||||
info!(context, "Message belongs to a different handshake (scaled up contact anyway to allow creation of group).");
|
||||
@@ -744,7 +751,7 @@ pub(crate) async fn handle_securejoin_handshake(
|
||||
==== Step 8 in "Out-of-band verified groups" protocol ====
|
||||
==========================================================*/
|
||||
|
||||
if let Ok(contact) = Contact::get_by_id(context, contact_id) {
|
||||
if let Ok(contact) = Contact::get_by_id(context, contact_id).await {
|
||||
if contact.is_verified(context) == VerifiedStatus::Unverified {
|
||||
warn!(context, "vg-member-added-received invalid.",);
|
||||
return Ok(HandshakeMessage::Ignore);
|
||||
@@ -756,6 +763,7 @@ pub(crate) async fn handle_securejoin_handshake(
|
||||
.map(|s| s.as_str())
|
||||
.unwrap_or_else(|| "");
|
||||
let (group_chat_id, _, _) = chat::get_chat_id_by_grpid(context, &field_grpid)
|
||||
.await
|
||||
.map_err(|err| {
|
||||
warn!(context, "Failed to lookup chat_id from grpid: {}", err);
|
||||
HandshakeError::ChatNotFound {
|
||||
@@ -868,18 +876,25 @@ fn encrypted_and_signed(
|
||||
}
|
||||
}
|
||||
|
||||
pub fn handle_degrade_event(context: &Context, peerstate: &Peerstate) -> Result<(), Error> {
|
||||
pub async fn handle_degrade_event(
|
||||
context: &Context,
|
||||
peerstate: &Peerstate<'_>,
|
||||
) -> Result<(), Error> {
|
||||
// - we do not issue an warning for DC_DE_ENCRYPTION_PAUSED as this is quite normal
|
||||
// - currently, we do not issue an extra warning for DC_DE_VERIFICATION_LOST - this always comes
|
||||
// together with DC_DE_FINGERPRINT_CHANGED which is logged, the idea is not to bother
|
||||
// with things they cannot fix, so the user is just kicked from the verified group
|
||||
// (and he will know this and can fix this)
|
||||
if Some(DegradeEvent::FingerprintChanged) == peerstate.degrade_event {
|
||||
let contact_id: i32 = match context.sql.query_get_value(
|
||||
context,
|
||||
"SELECT id FROM contacts WHERE addr=?;",
|
||||
params![&peerstate.addr],
|
||||
) {
|
||||
let contact_id: i32 = match context
|
||||
.sql
|
||||
.query_get_value(
|
||||
context,
|
||||
"SELECT id FROM contacts WHERE addr=?;",
|
||||
params![&peerstate.addr],
|
||||
)
|
||||
.await
|
||||
{
|
||||
None => bail!(
|
||||
"contact with peerstate.addr {:?} not found",
|
||||
&peerstate.addr
|
||||
@@ -889,6 +904,7 @@ pub fn handle_degrade_event(context: &Context, peerstate: &Peerstate) -> Result<
|
||||
if contact_id > 0 {
|
||||
let (contact_chat_id, _) =
|
||||
chat::create_or_lookup_by_contact_id(context, contact_id as u32, Blocked::Deaddrop)
|
||||
.await
|
||||
.unwrap_or_default();
|
||||
|
||||
let msg = context
|
||||
|
||||
1624
src/sql.rs
1624
src/sql.rs
File diff suppressed because it is too large
Load Diff
@@ -544,17 +544,17 @@ mod tests {
|
||||
async fn test_update_device_chats() {
|
||||
let t = dummy_context();
|
||||
t.ctx.update_device_chats().ok();
|
||||
let chats = Chatlist::try_load(&t.ctx, 0, None, None).unwrap();
|
||||
let chats = Chatlist::try_load(&t.ctx, 0, None, None).await.unwrap();
|
||||
assert_eq!(chats.len(), 2);
|
||||
|
||||
chats.get_chat_id(0).delete(&t.ctx).await.ok();
|
||||
chats.get_chat_id(1).delete(&t.ctx).await.ok();
|
||||
let chats = Chatlist::try_load(&t.ctx, 0, None, None).unwrap();
|
||||
let chats = Chatlist::try_load(&t.ctx, 0, None, None).await.unwrap();
|
||||
assert_eq!(chats.len(), 0);
|
||||
|
||||
// a subsequent call to update_device_chats() must not re-add manally deleted messages or chats
|
||||
t.ctx.update_device_chats().ok();
|
||||
let chats = Chatlist::try_load(&t.ctx, 0, None, None).unwrap();
|
||||
let chats = Chatlist::try_load(&t.ctx, 0, None, None).await.unwrap();
|
||||
assert_eq!(chats.len(), 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,6 +83,7 @@ pub(crate) async fn configure_alice_keypair(ctx: &Context) -> String {
|
||||
.await
|
||||
.unwrap();
|
||||
key::store_self_keypair(&ctx, &keypair, key::KeyPairUse::Default)
|
||||
.await
|
||||
.expect("Failed to save Alice's key");
|
||||
keypair.addr.to_string()
|
||||
}
|
||||
|
||||
44
src/token.rs
44
src/token.rs
@@ -9,7 +9,6 @@ use deltachat_derive::*;
|
||||
use crate::chat::ChatId;
|
||||
use crate::context::Context;
|
||||
use crate::dc_tools::*;
|
||||
use crate::sql;
|
||||
|
||||
/// Token namespace
|
||||
#[derive(Debug, Display, Clone, Copy, PartialEq, Eq, FromPrimitive, ToPrimitive, ToSql, FromSql)]
|
||||
@@ -28,37 +27,46 @@ impl Default for Namespace {
|
||||
|
||||
/// Creates a new token and saves it into the database.
|
||||
/// Returns created token.
|
||||
pub fn save(context: &Context, namespace: Namespace, foreign_id: ChatId) -> String {
|
||||
pub async fn save(context: &Context, namespace: Namespace, foreign_id: ChatId) -> String {
|
||||
// foreign_id may be 0
|
||||
let token = dc_create_id();
|
||||
sql::execute(
|
||||
context,
|
||||
&context.sql,
|
||||
"INSERT INTO tokens (namespc, foreign_id, token, timestamp) VALUES (?, ?, ?, ?);",
|
||||
params![namespace, foreign_id, &token, time()],
|
||||
)
|
||||
.ok();
|
||||
context
|
||||
.sql
|
||||
.execute(
|
||||
"INSERT INTO tokens (namespc, foreign_id, token, timestamp) VALUES (?, ?, ?, ?);",
|
||||
params![namespace, foreign_id, &token, time()],
|
||||
)
|
||||
.await
|
||||
.ok();
|
||||
token
|
||||
}
|
||||
|
||||
pub fn lookup(context: &Context, namespace: Namespace, foreign_id: ChatId) -> Option<String> {
|
||||
context.sql.query_get_value::<_, String>(
|
||||
context,
|
||||
"SELECT token FROM tokens WHERE namespc=? AND foreign_id=?;",
|
||||
params![namespace, foreign_id],
|
||||
)
|
||||
pub async fn lookup(context: &Context, namespace: Namespace, foreign_id: ChatId) -> Option<String> {
|
||||
context
|
||||
.sql
|
||||
.query_get_value::<_, String>(
|
||||
context,
|
||||
"SELECT token FROM tokens WHERE namespc=? AND foreign_id=?;",
|
||||
params![namespace, foreign_id],
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub fn lookup_or_new(context: &Context, namespace: Namespace, foreign_id: ChatId) -> String {
|
||||
lookup(context, namespace, foreign_id).unwrap_or_else(|| save(context, namespace, foreign_id))
|
||||
pub async fn lookup_or_new(context: &Context, namespace: Namespace, foreign_id: ChatId) -> String {
|
||||
if let Some(token) = lookup(context, namespace, foreign_id).await {
|
||||
return token;
|
||||
}
|
||||
|
||||
save(context, namespace, foreign_id).await
|
||||
}
|
||||
|
||||
pub fn exists(context: &Context, namespace: Namespace, token: &str) -> bool {
|
||||
pub async fn exists(context: &Context, namespace: Namespace, token: &str) -> bool {
|
||||
context
|
||||
.sql
|
||||
.exists(
|
||||
"SELECT id FROM tokens WHERE namespc=? AND token=?;",
|
||||
params![namespace, token],
|
||||
)
|
||||
.await
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user