mirror of
https://github.com/chatmail/core.git
synced 2026-04-17 21:46:35 +03:00
606 lines
17 KiB
Rust
606 lines
17 KiB
Rust
use std::sync::{Arc, Condvar, Mutex, RwLock};
|
|
|
|
use crate::constants::*;
|
|
use crate::contact::*;
|
|
use crate::dc_array::*;
|
|
use crate::dc_chat::*;
|
|
use crate::dc_job::*;
|
|
use crate::dc_jobthread::*;
|
|
use crate::dc_loginparam::*;
|
|
use crate::dc_lot::dc_lot_t;
|
|
use crate::dc_move::*;
|
|
use crate::dc_msg::*;
|
|
use crate::dc_receive_imf::*;
|
|
use crate::dc_tools::*;
|
|
use crate::imap::*;
|
|
use crate::key::*;
|
|
use crate::param::Params;
|
|
use crate::smtp::*;
|
|
use crate::sql::Sql;
|
|
use crate::types::*;
|
|
use crate::x::*;
|
|
|
|
pub struct Context {
|
|
pub userdata: *mut libc::c_void,
|
|
pub dbfile: Arc<RwLock<*mut libc::c_char>>,
|
|
pub blobdir: Arc<RwLock<*mut libc::c_char>>,
|
|
pub sql: Sql,
|
|
pub inbox: Arc<RwLock<Imap>>,
|
|
pub perform_inbox_jobs_needed: Arc<RwLock<bool>>,
|
|
pub probe_imap_network: Arc<RwLock<bool>>,
|
|
pub sentbox_thread: Arc<RwLock<dc_jobthread_t>>,
|
|
pub mvbox_thread: Arc<RwLock<dc_jobthread_t>>,
|
|
pub smtp: Arc<Mutex<Smtp>>,
|
|
pub smtp_state: Arc<(Mutex<SmtpState>, Condvar)>,
|
|
pub oauth2_critical: Arc<Mutex<()>>,
|
|
pub cb: Option<dc_callback_t>,
|
|
pub os_name: Option<String>,
|
|
pub cmdline_sel_chat_id: Arc<RwLock<u32>>,
|
|
pub bob: Arc<RwLock<BobStatus>>,
|
|
pub last_smeared_timestamp: Arc<RwLock<i64>>,
|
|
pub running_state: Arc<RwLock<RunningState>>,
|
|
}
|
|
|
|
unsafe impl std::marker::Send for Context {}
|
|
unsafe impl std::marker::Sync for Context {}
|
|
|
|
#[derive(Debug, PartialEq, Eq)]
|
|
pub struct RunningState {
|
|
pub ongoing_running: bool,
|
|
pub shall_stop_ongoing: bool,
|
|
}
|
|
|
|
impl Context {
|
|
pub fn has_dbfile(&self) -> bool {
|
|
!self.get_dbfile().is_null()
|
|
}
|
|
|
|
pub fn has_blobdir(&self) -> bool {
|
|
!self.get_blobdir().is_null()
|
|
}
|
|
|
|
pub fn get_dbfile(&self) -> *const libc::c_char {
|
|
*self.dbfile.clone().read().unwrap()
|
|
}
|
|
|
|
pub fn get_blobdir(&self) -> *const libc::c_char {
|
|
*self.blobdir.clone().read().unwrap()
|
|
}
|
|
|
|
pub fn call_cb(&self, event: Event, data1: uintptr_t, data2: uintptr_t) -> uintptr_t {
|
|
if let Some(cb) = self.cb {
|
|
unsafe { cb(self, event, data1, data2) }
|
|
} else {
|
|
0
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Drop for Context {
|
|
fn drop(&mut self) {
|
|
unsafe {
|
|
dc_close(&self);
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Default for RunningState {
|
|
fn default() -> Self {
|
|
RunningState {
|
|
ongoing_running: false,
|
|
shall_stop_ongoing: true,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, PartialEq, Eq)]
|
|
pub struct BobStatus {
|
|
pub expects: i32,
|
|
pub status: i32,
|
|
pub qr_scan: *mut dc_lot_t,
|
|
}
|
|
|
|
impl Default for BobStatus {
|
|
fn default() -> Self {
|
|
BobStatus {
|
|
expects: 0,
|
|
status: 0,
|
|
qr_scan: std::ptr::null_mut(),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Default, Debug)]
|
|
pub struct SmtpState {
|
|
pub idle: bool,
|
|
pub suspended: bool,
|
|
pub doing_jobs: bool,
|
|
pub perform_jobs_needed: i32,
|
|
pub probe_network: bool,
|
|
}
|
|
|
|
// create/open/config/information
|
|
pub fn dc_context_new(
|
|
cb: Option<dc_callback_t>,
|
|
userdata: *mut libc::c_void,
|
|
os_name: Option<String>,
|
|
) -> Context {
|
|
Context {
|
|
blobdir: Arc::new(RwLock::new(std::ptr::null_mut())),
|
|
dbfile: Arc::new(RwLock::new(std::ptr::null_mut())),
|
|
inbox: Arc::new(RwLock::new({
|
|
Imap::new(
|
|
cb_get_config,
|
|
cb_set_config,
|
|
cb_precheck_imf,
|
|
cb_receive_imf,
|
|
)
|
|
})),
|
|
userdata,
|
|
cb,
|
|
os_name: os_name,
|
|
running_state: Arc::new(RwLock::new(Default::default())),
|
|
sql: Sql::new(),
|
|
smtp: Arc::new(Mutex::new(Smtp::new())),
|
|
smtp_state: Arc::new((Mutex::new(Default::default()), Condvar::new())),
|
|
oauth2_critical: Arc::new(Mutex::new(())),
|
|
bob: Arc::new(RwLock::new(Default::default())),
|
|
last_smeared_timestamp: Arc::new(RwLock::new(0)),
|
|
cmdline_sel_chat_id: Arc::new(RwLock::new(0)),
|
|
sentbox_thread: Arc::new(RwLock::new(dc_jobthread_init(
|
|
"SENTBOX",
|
|
"configured_sentbox_folder",
|
|
Imap::new(
|
|
cb_get_config,
|
|
cb_set_config,
|
|
cb_precheck_imf,
|
|
cb_receive_imf,
|
|
),
|
|
))),
|
|
mvbox_thread: Arc::new(RwLock::new(dc_jobthread_init(
|
|
"MVBOX",
|
|
"configured_mvbox_folder",
|
|
Imap::new(
|
|
cb_get_config,
|
|
cb_set_config,
|
|
cb_precheck_imf,
|
|
cb_receive_imf,
|
|
),
|
|
))),
|
|
probe_imap_network: Arc::new(RwLock::new(false)),
|
|
perform_inbox_jobs_needed: Arc::new(RwLock::new(false)),
|
|
}
|
|
}
|
|
|
|
unsafe fn cb_receive_imf(
|
|
context: &Context,
|
|
imf_raw_not_terminated: *const libc::c_char,
|
|
imf_raw_bytes: size_t,
|
|
server_folder: &str,
|
|
server_uid: uint32_t,
|
|
flags: uint32_t,
|
|
) {
|
|
dc_receive_imf(
|
|
context,
|
|
imf_raw_not_terminated,
|
|
imf_raw_bytes,
|
|
server_folder,
|
|
server_uid,
|
|
flags,
|
|
);
|
|
}
|
|
|
|
unsafe fn cb_precheck_imf(
|
|
context: &Context,
|
|
rfc724_mid: *const libc::c_char,
|
|
server_folder: &str,
|
|
server_uid: uint32_t,
|
|
) -> libc::c_int {
|
|
let mut rfc724_mid_exists: libc::c_int = 0i32;
|
|
let msg_id: uint32_t;
|
|
let mut old_server_folder: *mut libc::c_char = 0 as *mut libc::c_char;
|
|
let mut old_server_uid: uint32_t = 0i32 as uint32_t;
|
|
let mut mark_seen: libc::c_int = 0i32;
|
|
msg_id = dc_rfc724_mid_exists(
|
|
context,
|
|
rfc724_mid,
|
|
&mut old_server_folder,
|
|
&mut old_server_uid,
|
|
);
|
|
if msg_id != 0i32 as libc::c_uint {
|
|
rfc724_mid_exists = 1i32;
|
|
if *old_server_folder.offset(0isize) as libc::c_int == 0i32
|
|
&& old_server_uid == 0i32 as libc::c_uint
|
|
{
|
|
info!(
|
|
context,
|
|
0,
|
|
"[move] detected bbc-self {}",
|
|
as_str(rfc724_mid),
|
|
);
|
|
mark_seen = 1i32
|
|
} else if as_str(old_server_folder) != server_folder {
|
|
info!(
|
|
context,
|
|
0,
|
|
"[move] detected moved message {}",
|
|
as_str(rfc724_mid),
|
|
);
|
|
dc_update_msg_move_state(context, rfc724_mid, MoveState::Stay);
|
|
}
|
|
if as_str(old_server_folder) != server_folder || old_server_uid != server_uid {
|
|
dc_update_server_uid(context, rfc724_mid, server_folder, server_uid);
|
|
}
|
|
dc_do_heuristics_moves(context, server_folder, msg_id);
|
|
if 0 != mark_seen {
|
|
dc_job_add(context, 130, msg_id as libc::c_int, Params::new(), 0);
|
|
}
|
|
}
|
|
free(old_server_folder as *mut libc::c_void);
|
|
return rfc724_mid_exists;
|
|
}
|
|
|
|
fn cb_set_config(context: &Context, key: &str, value: Option<&str>) {
|
|
context.sql.set_config(context, key, value).ok();
|
|
}
|
|
|
|
/* *
|
|
* The following three callback are given to dc_imap_new() to read/write configuration
|
|
* and to handle received messages. As the imap-functions are typically used in
|
|
* a separate user-thread, also these functions may be called from a different thread.
|
|
*
|
|
* @private @memberof Context
|
|
*/
|
|
fn cb_get_config(context: &Context, key: &str) -> Option<String> {
|
|
context.sql.get_config(context, key)
|
|
}
|
|
|
|
pub unsafe fn dc_close(context: &Context) {
|
|
info!(context, 0, "disconnecting INBOX-watch",);
|
|
context.inbox.read().unwrap().disconnect(context);
|
|
info!(context, 0, "disconnecting sentbox-thread",);
|
|
context
|
|
.sentbox_thread
|
|
.read()
|
|
.unwrap()
|
|
.imap
|
|
.disconnect(context);
|
|
info!(context, 0, "disconnecting mvbox-thread",);
|
|
context
|
|
.mvbox_thread
|
|
.read()
|
|
.unwrap()
|
|
.imap
|
|
.disconnect(context);
|
|
|
|
info!(context, 0, "disconnecting SMTP");
|
|
context.smtp.clone().lock().unwrap().disconnect();
|
|
|
|
context.sql.close(context);
|
|
let mut dbfile = context.dbfile.write().unwrap();
|
|
free(*dbfile as *mut libc::c_void);
|
|
*dbfile = 0 as *mut libc::c_char;
|
|
let mut blobdir = context.blobdir.write().unwrap();
|
|
free(*blobdir as *mut libc::c_void);
|
|
*blobdir = 0 as *mut libc::c_char;
|
|
}
|
|
|
|
pub unsafe fn dc_is_open(context: &Context) -> libc::c_int {
|
|
match context.sql.is_open() {
|
|
true => 1,
|
|
false => 0,
|
|
}
|
|
}
|
|
|
|
pub unsafe fn dc_get_userdata(context: &mut Context) -> *mut libc::c_void {
|
|
context.userdata as *mut _
|
|
}
|
|
|
|
pub unsafe fn dc_open(context: &Context, dbfile: &str, blobdir: Option<&str>) -> bool {
|
|
let mut success = false;
|
|
if 0 != dc_is_open(context) {
|
|
return false;
|
|
}
|
|
*context.dbfile.write().unwrap() = dbfile.strdup();
|
|
if blobdir.is_some() && blobdir.unwrap().len() > 0 {
|
|
let dir = dc_ensure_no_slash_safe(blobdir.unwrap()).strdup();
|
|
*context.blobdir.write().unwrap() = dir;
|
|
} else {
|
|
let dir = dbfile.to_string() + "-blobs";
|
|
dc_create_folder(context, &dir);
|
|
*context.blobdir.write().unwrap() = dir.strdup();
|
|
}
|
|
// Create/open sqlite database, this may already use the blobdir
|
|
let dbfile_path = std::path::Path::new(dbfile);
|
|
if context.sql.open(context, dbfile_path, 0) {
|
|
success = true
|
|
}
|
|
if !success {
|
|
dc_close(context);
|
|
}
|
|
success
|
|
}
|
|
|
|
pub unsafe fn dc_get_blobdir(context: &Context) -> *mut libc::c_char {
|
|
dc_strdup(*context.blobdir.clone().read().unwrap())
|
|
}
|
|
|
|
/* ******************************************************************************
|
|
* INI-handling, Information
|
|
******************************************************************************/
|
|
|
|
pub unsafe fn dc_get_info(context: &Context) -> *mut libc::c_char {
|
|
let unset = "0";
|
|
let l = dc_loginparam_read(context, &context.sql, "");
|
|
let l2 = dc_loginparam_read(context, &context.sql, "configured_");
|
|
let displayname = context.sql.get_config(context, "displayname");
|
|
let chats = dc_get_chat_cnt(context) as usize;
|
|
let real_msgs = dc_get_real_msg_cnt(context) as usize;
|
|
let deaddrop_msgs = dc_get_deaddrop_msg_cnt(context) as usize;
|
|
let contacts = Contact::get_real_cnt(context) as usize;
|
|
let is_configured = context
|
|
.sql
|
|
.get_config_int(context, "configured")
|
|
.unwrap_or_default();
|
|
let dbversion = context
|
|
.sql
|
|
.get_config_int(context, "dbversion")
|
|
.unwrap_or_default();
|
|
let e2ee_enabled = context
|
|
.sql
|
|
.get_config_int(context, "e2ee_enabled")
|
|
.unwrap_or_else(|| 1);
|
|
let mdns_enabled = context
|
|
.sql
|
|
.get_config_int(context, "mdns_enabled")
|
|
.unwrap_or_else(|| 1);
|
|
|
|
let prv_key_cnt: Option<isize> = context.sql.query_row_col(
|
|
context,
|
|
"SELECT COUNT(*) FROM keypairs;",
|
|
rusqlite::NO_PARAMS,
|
|
0,
|
|
);
|
|
|
|
let pub_key_cnt: Option<isize> = context.sql.query_row_col(
|
|
context,
|
|
"SELECT COUNT(*) FROM acpeerstates;",
|
|
rusqlite::NO_PARAMS,
|
|
0,
|
|
);
|
|
|
|
let fingerprint_str = if let Some(key) = Key::from_self_public(context, &l2.addr, &context.sql)
|
|
{
|
|
key.fingerprint()
|
|
} else {
|
|
"<Not yet calculated>".into()
|
|
};
|
|
|
|
let l_readable_str = dc_loginparam_get_readable(&l);
|
|
let l2_readable_str = dc_loginparam_get_readable(&l2);
|
|
let inbox_watch = context
|
|
.sql
|
|
.get_config_int(context, "inbox_watch")
|
|
.unwrap_or_else(|| 1);
|
|
let sentbox_watch = context
|
|
.sql
|
|
.get_config_int(context, "sentbox_watch")
|
|
.unwrap_or_else(|| 1);
|
|
let mvbox_watch = context
|
|
.sql
|
|
.get_config_int(context, "mvbox_watch")
|
|
.unwrap_or_else(|| 1);
|
|
let mvbox_move = context
|
|
.sql
|
|
.get_config_int(context, "mvbox_move")
|
|
.unwrap_or_else(|| 1);
|
|
let folders_configured = context
|
|
.sql
|
|
.get_config_int(context, "folders_configured")
|
|
.unwrap_or_default();
|
|
let configured_sentbox_folder = context
|
|
.sql
|
|
.get_config(context, "configured_sentbox_folder")
|
|
.unwrap_or_else(|| "<unset>".to_string());
|
|
let configured_mvbox_folder = context
|
|
.sql
|
|
.get_config(context, "configured_mvbox_folder")
|
|
.unwrap_or_else(|| "<unset>".to_string());
|
|
|
|
let res = format!(
|
|
"deltachat_core_version=v{}\n\
|
|
sqlite_version={}\n\
|
|
sqlite_thread_safe={}\n\
|
|
arch={}\n\
|
|
number_of_chats={}\n\
|
|
number_of_chat_messages={}\n\
|
|
messages_in_contact_requests={}\n\
|
|
number_of_contacts={}\n\
|
|
database_dir={}\n\
|
|
database_version={}\n\
|
|
blobdir={}\n\
|
|
display_name={}\n\
|
|
is_configured={}\n\
|
|
entered_account_settings={}\n\
|
|
used_account_settings={}\n\
|
|
inbox_watch={}\n\
|
|
sentbox_watch={}\n\
|
|
mvbox_watch={}\n\
|
|
mvbox_move={}\n\
|
|
folders_configured={}\n\
|
|
configured_sentbox_folder={}\n\
|
|
configured_mvbox_folder={}\n\
|
|
mdns_enabled={}\n\
|
|
e2ee_enabled={}\n\
|
|
private_key_count={}\n\
|
|
public_key_count={}\n\
|
|
fingerprint={}\n\
|
|
level=awesome\n",
|
|
as_str(DC_VERSION_STR as *const u8 as *const _),
|
|
rusqlite::version(),
|
|
sqlite3_threadsafe(),
|
|
// arch
|
|
(::std::mem::size_of::<*mut libc::c_void>()).wrapping_mul(8),
|
|
chats,
|
|
real_msgs,
|
|
deaddrop_msgs,
|
|
contacts,
|
|
if context.has_dbfile() {
|
|
as_str(context.get_dbfile())
|
|
} else {
|
|
unset
|
|
},
|
|
dbversion,
|
|
if context.has_blobdir() {
|
|
as_str(context.get_blobdir())
|
|
} else {
|
|
unset
|
|
},
|
|
displayname.unwrap_or_else(|| unset.into()),
|
|
is_configured,
|
|
l_readable_str,
|
|
l2_readable_str,
|
|
inbox_watch,
|
|
sentbox_watch,
|
|
mvbox_watch,
|
|
mvbox_move,
|
|
folders_configured,
|
|
configured_sentbox_folder,
|
|
configured_mvbox_folder,
|
|
mdns_enabled,
|
|
e2ee_enabled,
|
|
prv_key_cnt.unwrap_or_default(),
|
|
pub_key_cnt.unwrap_or_default(),
|
|
fingerprint_str,
|
|
);
|
|
|
|
res.strdup()
|
|
}
|
|
|
|
pub unsafe fn dc_get_version_str() -> *mut libc::c_char {
|
|
dc_strdup(DC_VERSION_STR as *const u8 as *const libc::c_char)
|
|
}
|
|
|
|
pub fn dc_get_fresh_msgs(context: &Context) -> *mut dc_array_t {
|
|
let show_deaddrop = 0;
|
|
|
|
context
|
|
.sql
|
|
.query_map(
|
|
"SELECT m.id FROM msgs m LEFT JOIN contacts ct \
|
|
ON m.from_id=ct.id LEFT JOIN chats c ON m.chat_id=c.id WHERE m.state=? \
|
|
AND m.hidden=0 \
|
|
AND m.chat_id>? \
|
|
AND ct.blocked=0 \
|
|
AND (c.blocked=0 OR c.blocked=?) ORDER BY m.timestamp DESC,m.id DESC;",
|
|
&[10, 9, if 0 != show_deaddrop { 2 } else { 0 }],
|
|
|row| row.get(0),
|
|
|rows| {
|
|
let mut ret = dc_array_t::new(128);
|
|
|
|
for row in rows {
|
|
let id = row?;
|
|
ret.add_id(id);
|
|
}
|
|
Ok(ret.into_raw())
|
|
},
|
|
)
|
|
.unwrap()
|
|
}
|
|
|
|
#[allow(non_snake_case)]
|
|
pub fn dc_search_msgs(
|
|
context: &Context,
|
|
chat_id: uint32_t,
|
|
query: *const libc::c_char,
|
|
) -> *mut dc_array_t {
|
|
if query.is_null() {
|
|
return std::ptr::null_mut();
|
|
}
|
|
|
|
let real_query = to_string(query).trim().to_string();
|
|
if real_query.is_empty() {
|
|
return std::ptr::null_mut();
|
|
}
|
|
let strLikeInText = format!("%{}%", &real_query);
|
|
let strLikeBeg = format!("{}%", &real_query);
|
|
|
|
let query = if 0 != chat_id {
|
|
"SELECT m.id, m.timestamp FROM msgs m LEFT JOIN contacts ct ON m.from_id=ct.id WHERE m.chat_id=? \
|
|
AND m.hidden=0 \
|
|
AND ct.blocked=0 AND (txt LIKE ? OR ct.name LIKE ?) ORDER BY m.timestamp,m.id;"
|
|
} else {
|
|
"SELECT m.id, m.timestamp FROM msgs m LEFT JOIN contacts ct ON m.from_id=ct.id \
|
|
LEFT JOIN chats c ON m.chat_id=c.id WHERE m.chat_id>9 AND m.hidden=0 \
|
|
AND (c.blocked=0 OR c.blocked=?) \
|
|
AND ct.blocked=0 AND (m.txt LIKE ? OR ct.name LIKE ?) ORDER BY m.timestamp DESC,m.id DESC;"
|
|
};
|
|
|
|
let mut ret = dc_array_t::new(100);
|
|
|
|
let success = context
|
|
.sql
|
|
.query_map(
|
|
query,
|
|
params![chat_id as libc::c_int, &strLikeInText, &strLikeBeg],
|
|
|row| row.get::<_, i32>(0),
|
|
|rows| {
|
|
for id in rows {
|
|
ret.add_id(id? as u32);
|
|
}
|
|
Ok(())
|
|
},
|
|
)
|
|
.is_ok();
|
|
|
|
if success {
|
|
return ret.into_raw();
|
|
}
|
|
|
|
std::ptr::null_mut()
|
|
}
|
|
|
|
pub fn dc_is_inbox(_context: &Context, folder_name: impl AsRef<str>) -> bool {
|
|
folder_name.as_ref() == "INBOX"
|
|
}
|
|
|
|
pub fn dc_is_sentbox(context: &Context, folder_name: impl AsRef<str>) -> bool {
|
|
let sentbox_name = context.sql.get_config(context, "configured_sentbox_folder");
|
|
if let Some(name) = sentbox_name {
|
|
name == folder_name.as_ref()
|
|
} else {
|
|
false
|
|
}
|
|
}
|
|
|
|
pub fn dc_is_mvbox(context: &Context, folder_name: impl AsRef<str>) -> bool {
|
|
let mvbox_name = context.sql.get_config(context, "configured_mvbox_folder");
|
|
|
|
if let Some(name) = mvbox_name {
|
|
name == folder_name.as_ref()
|
|
} else {
|
|
false
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn no_crashes_on_context_deref() {
|
|
let ctx = dc_context_new(None, std::ptr::null_mut(), Some("Test OS".into()));
|
|
std::mem::drop(ctx);
|
|
}
|
|
|
|
#[test]
|
|
fn test_context_double_close() {
|
|
let ctx = dc_context_new(None, std::ptr::null_mut(), None);
|
|
unsafe {
|
|
dc_close(&ctx);
|
|
dc_close(&ctx);
|
|
}
|
|
std::mem::drop(ctx);
|
|
}
|
|
}
|