start making sql async

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

View File

@@ -49,7 +49,7 @@ async fn main() {
let ctx1 = ctx.clone();
let 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;

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -81,7 +81,7 @@ pub async fn imex(context: &Context, what: ImexMode, param1: Option<impl AsRef<P
}
/// Returns the filename of the backup found (otherwise an error)
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>"));
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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