diff --git a/deltachat-ffi/src/lib.rs b/deltachat-ffi/src/lib.rs index 94c27c228..843d0cdba 100644 --- a/deltachat-ffi/src/lib.rs +++ b/deltachat-ffi/src/lib.rs @@ -13,6 +13,7 @@ extern crate num_traits; use num_traits::{FromPrimitive, ToPrimitive}; use std::convert::TryInto; +use std::ptr; use std::str::FromStr; use deltachat::contact::Contact; @@ -322,7 +323,7 @@ pub unsafe extern "C" fn dc_create_chat_by_msg_id(context: *mut dc_context_t, ms assert!(!context.is_null()); let context = &*context; - dc_chat::dc_create_chat_by_msg_id(context, msg_id) + chat::create_by_msg_id(context, msg_id).unwrap_or_log_default(context, "Failed to create chat") } #[no_mangle] @@ -333,7 +334,8 @@ pub unsafe extern "C" fn dc_create_chat_by_contact_id( assert!(!context.is_null()); let context = &*context; - dc_chat::dc_create_chat_by_contact_id(context, contact_id) + chat::create_by_contact_id(context, contact_id) + .unwrap_or_log_default(context, "Failed to create chat") } #[no_mangle] @@ -344,7 +346,8 @@ pub unsafe extern "C" fn dc_get_chat_id_by_contact_id( assert!(!context.is_null()); let context = &*context; - dc_chat::dc_get_chat_id_by_contact_id(context, contact_id) + chat::get_by_contact_id(context, contact_id) + .unwrap_or_log_default(context, "Failed to get chat") } #[no_mangle] @@ -357,7 +360,8 @@ pub unsafe extern "C" fn dc_prepare_msg( assert!(!msg.is_null()); let context = &*context; - dc_chat::dc_prepare_msg(context, chat_id, msg) + chat::prepare_msg(context, chat_id, msg) + .unwrap_or_log_default(context, "Failed to prepare message") } #[no_mangle] @@ -370,7 +374,7 @@ pub unsafe extern "C" fn dc_send_msg( assert!(!msg.is_null()); let context = &*context; - dc_chat::dc_send_msg(context, chat_id, msg) + chat::send_msg(context, chat_id, msg).unwrap_or_log_default(context, "Failed to send message") } #[no_mangle] @@ -384,7 +388,8 @@ pub unsafe extern "C" fn dc_send_text_msg( let context = &*context; let text_to_send = dc_tools::to_string_lossy(text_to_send); - dc_chat::dc_send_text_msg(context, chat_id, text_to_send) + chat::send_text_msg(context, chat_id, text_to_send) + .unwrap_or_log_default(context, "Failed to send text message") } #[no_mangle] @@ -396,7 +401,7 @@ pub unsafe extern "C" fn dc_set_draft( assert!(!context.is_null()); let context = &*context; - dc_chat::dc_set_draft(context, chat_id, msg) + chat::set_draft(context, chat_id, msg) } #[no_mangle] @@ -407,7 +412,7 @@ pub unsafe extern "C" fn dc_get_draft<'a>( assert!(!context.is_null()); let context = &*context; - dc_chat::dc_get_draft(context, chat_id) + chat::get_draft(context, chat_id) } #[no_mangle] @@ -420,7 +425,7 @@ pub unsafe extern "C" fn dc_get_chat_msgs( assert!(!context.is_null()); let context = &*context; - dc_chat::dc_get_chat_msgs(context, chat_id, flags, marker1before) + chat::get_chat_msgs(context, chat_id, flags, marker1before) } #[no_mangle] @@ -428,7 +433,7 @@ pub unsafe extern "C" fn dc_get_msg_cnt(context: *mut dc_context_t, chat_id: u32 assert!(!context.is_null()); let context = &*context; - dc_chat::dc_get_msg_cnt(context, chat_id) + chat::get_msg_cnt(context, chat_id) as libc::c_int } #[no_mangle] @@ -439,7 +444,7 @@ pub unsafe extern "C" fn dc_get_fresh_msg_cnt( assert!(!context.is_null()); let context = &*context; - dc_chat::dc_get_fresh_msg_cnt(context, chat_id) + chat::get_fresh_msg_cnt(context, chat_id) as libc::c_int } #[no_mangle] @@ -457,7 +462,7 @@ pub unsafe extern "C" fn dc_marknoticed_chat(context: *mut dc_context_t, chat_id assert!(!context.is_null()); let context = &*context; - dc_chat::dc_marknoticed_chat(context, chat_id); + chat::marknoticed_chat(context, chat_id).log_err(context, "Failed marknoticed chat"); } #[no_mangle] @@ -465,7 +470,7 @@ pub unsafe extern "C" fn dc_marknoticed_all_chats(context: *mut dc_context_t) { assert!(!context.is_null()); let context = &*context; - dc_chat::dc_marknoticed_all_chats(context); + chat::marknoticed_all_chats(context).log_err(context, "Failed marknoticed all chats"); } fn from_prim(s: S) -> Option @@ -493,7 +498,7 @@ pub unsafe extern "C" fn dc_get_chat_media( let or_msg_type3 = from_prim(or_msg_type3).expect(&format!("incorrect or_msg_type3 = {}", or_msg_type3)); - dc_chat::dc_get_chat_media(context, chat_id, msg_type, or_msg_type2, or_msg_type3) + chat::get_chat_media(context, chat_id, msg_type, or_msg_type2, or_msg_type3) } #[no_mangle] @@ -514,7 +519,7 @@ pub unsafe extern "C" fn dc_get_next_media( let or_msg_type3 = from_prim(or_msg_type3).expect(&format!("incorrect or_msg_type3 = {}", or_msg_type3)); - dc_chat::dc_get_next_media(context, msg_id, dir, msg_type, or_msg_type2, or_msg_type3) + chat::get_next_media(context, msg_id, dir, msg_type, or_msg_type2, or_msg_type3) } #[no_mangle] @@ -526,7 +531,15 @@ pub unsafe extern "C" fn dc_archive_chat( assert!(!context.is_null()); let context = &*context; - dc_chat::dc_archive_chat(context, chat_id, archive); + let archive = if archive == 0 { + false + } else if archive == 1 { + true + } else { + return; + }; + + chat::archive(context, chat_id, archive).log_err(context, "Failed archive chat"); } #[no_mangle] @@ -534,8 +547,7 @@ pub unsafe extern "C" fn dc_delete_chat(context: *mut dc_context_t, chat_id: u32 assert!(!context.is_null()); let context = &*context; - // TODO: update to indicate public api success/failure of deletion - dc_chat::dc_delete_chat(context, chat_id); + chat::delete(context, chat_id).log_err(context, "Failed chat delete"); } #[no_mangle] @@ -546,7 +558,7 @@ pub unsafe extern "C" fn dc_get_chat_contacts( assert!(!context.is_null()); let context = &*context; - dc_array_t::from(dc_chat::dc_get_chat_contacts(context, chat_id)).into_raw() + dc_array_t::from(chat::get_chat_contacts(context, chat_id)).into_raw() } #[no_mangle] @@ -570,7 +582,10 @@ pub unsafe extern "C" fn dc_get_chat<'a>( assert!(!context.is_null()); let context = &*context; - dc_chat::dc_get_chat(context, chat_id) + match chat::Chat::load_from_db(context, chat_id) { + Ok(chat) => Box::into_raw(Box::new(chat)), + Err(_) => std::ptr::null_mut(), + } } #[no_mangle] @@ -583,7 +598,14 @@ pub unsafe extern "C" fn dc_create_group_chat( assert!(!name.is_null()); let context = &*context; - dc_chat::dc_create_group_chat(context, verified, name) + let verified = if let Some(s) = contact::VerifiedStatus::from_i32(verified) { + s + } else { + return 0; + }; + + chat::create_group_chat(context, verified, as_str(name)) + .unwrap_or_log_default(context, "Failed to create group chat") } #[no_mangle] @@ -595,7 +617,7 @@ pub unsafe extern "C" fn dc_is_contact_in_chat( assert!(!context.is_null()); let context = &*context; - dc_chat::dc_is_contact_in_chat(context, chat_id, contact_id) + chat::is_contact_in_chat(context, chat_id, contact_id) } #[no_mangle] @@ -607,7 +629,7 @@ pub unsafe extern "C" fn dc_add_contact_to_chat( assert!(!context.is_null()); let context = &*context; - dc_chat::dc_add_contact_to_chat(context, chat_id, contact_id) + chat::add_contact_to_chat(context, chat_id, contact_id) } #[no_mangle] @@ -619,7 +641,9 @@ pub unsafe extern "C" fn dc_remove_contact_from_chat( assert!(!context.is_null()); let context = &*context; - dc_chat::dc_remove_contact_from_chat(context, chat_id, contact_id) + chat::remove_contact_from_chat(context, chat_id, contact_id) + .map(|_| 1) + .unwrap_or_log_default(context, "Failed to remove contact") } #[no_mangle] @@ -633,7 +657,9 @@ pub unsafe extern "C" fn dc_set_chat_name( assert!(chat_id > constants::DC_CHAT_ID_LAST_SPECIAL as u32); let context = &*context; - dc_chat::dc_set_chat_name(context, chat_id, name) + chat::set_chat_name(context, chat_id, as_str(name)) + .map(|_| 1) + .unwrap_or_log_default(context, "Failed to set chat name") } #[no_mangle] @@ -646,7 +672,9 @@ pub unsafe extern "C" fn dc_set_chat_profile_image( assert!(chat_id > constants::DC_CHAT_ID_LAST_SPECIAL as u32); let context = &*context; - dc_chat::dc_set_chat_profile_image(context, chat_id, image) + chat::set_chat_profile_image(context, chat_id, as_str(image)) + .map(|_| 1) + .unwrap_or_log_default(context, "Failed to set profile image") } #[no_mangle] @@ -698,7 +726,7 @@ pub unsafe extern "C" fn dc_forward_msgs( assert!(chat_id > constants::DC_CHAT_ID_LAST_SPECIAL as u32); let context = &*context; - dc_chat::dc_forward_msgs(context, msg_ids, msg_cnt, chat_id) + chat::forward_msgs(context, msg_ids, msg_cnt, chat_id) } #[no_mangle] @@ -1239,7 +1267,9 @@ pub unsafe extern "C" fn dc_chatlist_get_summary<'a>( ) -> *mut dc_lot::dc_lot_t { assert!(!chatlist.is_null()); + let chat = if chat.is_null() { None } else { Some(&*chat) }; let list = &*chatlist; + list.get_summary(index as usize, chat) } @@ -1256,90 +1286,104 @@ pub unsafe extern "C" fn dc_chatlist_get_context( // dc_chat_t #[no_mangle] -pub type dc_chat_t<'a> = dc_chat::Chat<'a>; +pub type dc_chat_t<'a> = chat::Chat<'a>; #[no_mangle] pub unsafe extern "C" fn dc_chat_unref(chat: *mut dc_chat_t) { assert!(!chat.is_null()); - dc_chat::dc_chat_unref(chat) + Box::from_raw(chat); } #[no_mangle] pub unsafe extern "C" fn dc_chat_get_id(chat: *mut dc_chat_t) -> u32 { assert!(!chat.is_null()); + let chat = &*chat; - dc_chat::dc_chat_get_id(chat) + chat.get_id() } #[no_mangle] pub unsafe extern "C" fn dc_chat_get_type(chat: *mut dc_chat_t) -> libc::c_int { assert!(!chat.is_null()); + let chat = &*chat; - dc_chat::dc_chat_get_type(chat) + chat.get_type() as libc::c_int } #[no_mangle] pub unsafe extern "C" fn dc_chat_get_name(chat: *mut dc_chat_t) -> *mut libc::c_char { assert!(!chat.is_null()); + let chat = &*chat; - dc_chat::dc_chat_get_name(chat) + chat.get_name().strdup() } #[no_mangle] pub unsafe extern "C" fn dc_chat_get_subtitle(chat: *mut dc_chat_t) -> *mut libc::c_char { assert!(!chat.is_null()); + let chat = &*chat; - dc_chat::dc_chat_get_subtitle(chat) + chat.get_subtitle().strdup() } #[no_mangle] pub unsafe extern "C" fn dc_chat_get_profile_image(chat: *mut dc_chat_t) -> *mut libc::c_char { assert!(!chat.is_null()); + let chat = &*chat; - dc_chat::dc_chat_get_profile_image(chat) + match chat.get_profile_image() { + Some(i) => i.strdup(), + None => ptr::null_mut(), + } } #[no_mangle] pub unsafe extern "C" fn dc_chat_get_color(chat: *mut dc_chat_t) -> u32 { assert!(!chat.is_null()); + let chat = &*chat; - dc_chat::dc_chat_get_color(chat) + chat.get_color() } #[no_mangle] pub unsafe extern "C" fn dc_chat_get_archived(chat: *mut dc_chat_t) -> libc::c_int { assert!(!chat.is_null()); + let chat = &*chat; - dc_chat::dc_chat_get_archived(chat) + chat.is_archived() as libc::c_int } #[no_mangle] pub unsafe extern "C" fn dc_chat_is_unpromoted(chat: *mut dc_chat_t) -> libc::c_int { assert!(!chat.is_null()); + let chat = &*chat; - dc_chat::dc_chat_is_unpromoted(chat) + chat.is_unpromoted() as libc::c_int } #[no_mangle] pub unsafe extern "C" fn dc_chat_is_self_talk(chat: *mut dc_chat_t) -> libc::c_int { assert!(!chat.is_null()); + let chat = &*chat; - dc_chat::dc_chat_is_self_talk(chat) + chat.is_self_talk() as libc::c_int } #[no_mangle] pub unsafe extern "C" fn dc_chat_is_verified(chat: *mut dc_chat_t) -> libc::c_int { assert!(!chat.is_null()); + let chat = &*chat; - dc_chat::dc_chat_is_verified(chat) + chat.is_verified() as libc::c_int } #[no_mangle] pub unsafe extern "C" fn dc_chat_is_sending_locations(chat: *mut dc_chat_t) -> libc::c_int { assert!(!chat.is_null()); + let chat = &*chat; - dc_chat::dc_chat_is_sending_locations(chat) + chat.is_sending_locations() as libc::c_int } // dc_msg_t @@ -1500,6 +1544,7 @@ pub unsafe extern "C" fn dc_msg_get_summary<'a>( chat: *mut dc_chat_t<'a>, ) -> *mut dc_lot::dc_lot_t { assert!(!msg.is_null()); + let chat = if chat.is_null() { None } else { Some(&*chat) }; dc_msg::dc_msg_get_summary(msg, chat) } @@ -1819,3 +1864,26 @@ fn as_opt_str<'a>(s: *const libc::c_char) -> Option<&'a str> { Some(dc_tools::as_str(s)) } + +pub trait ResultExt { + fn unwrap_or_log_default(self, context: &context::Context, message: &str) -> T; + fn log_err(&self, context: &context::Context, message: &str); +} + +impl ResultExt for Result { + fn unwrap_or_log_default(self, context: &context::Context, message: &str) -> T { + match self { + Ok(t) => t, + Err(err) => { + error!(context, 0, "{}: {}", message, err); + Default::default() + } + } + } + + fn log_err(&self, context: &context::Context, message: &str) { + if let Err(err) = self { + error!(context, 0, "{}: {}", message, err); + } + } +} diff --git a/deltachat_derive/src/lib.rs b/deltachat_derive/src/lib.rs index 276b2085c..6fde011b7 100644 --- a/deltachat_derive/src/lib.rs +++ b/deltachat_derive/src/lib.rs @@ -20,7 +20,6 @@ pub fn to_sql_derive(input: TokenStream) -> TokenStream { let num = *self as i64; let value = rusqlite::types::Value::Integer(num); let output = rusqlite::types::ToSqlOutput::Owned(value); - std::result::Result::Ok(output) } } diff --git a/examples/repl/cmdline.rs b/examples/repl/cmdline.rs index 7939881f9..0e9843616 100644 --- a/examples/repl/cmdline.rs +++ b/examples/repl/cmdline.rs @@ -1,13 +1,13 @@ use std::ffi::CString; use std::str::FromStr; +use deltachat::chat::{self, Chat}; use deltachat::chatlist::*; use deltachat::config; use deltachat::constants::*; use deltachat::contact::*; use deltachat::context::*; use deltachat::dc_array::*; -use deltachat::dc_chat::*; use deltachat::dc_configure::*; use deltachat::dc_imex::*; use deltachat::dc_job::*; @@ -151,7 +151,7 @@ unsafe fn poke_spec(context: &Context, spec: *const libc::c_char) -> libc::c_int } if ok_to_continue { let ok_to_continue2; - suffix = dc_get_filesuffix_lc(real_spec); + suffix = dc_get_filesuffix_lc(as_str(real_spec)); if !suffix.is_null() && strcmp(suffix, b"eml\x00" as *const u8 as *const libc::c_char) == 0 { if 0 != dc_poke_eml_file(context, real_spec) { @@ -349,22 +349,16 @@ pub unsafe fn dc_cmdline_skip_auth() { S_IS_AUTH = 1; } -unsafe fn chat_prefix(chat: *const Chat) -> &'static str { - if (*chat).type_0 == 120 { - "Group" - } else if (*chat).type_0 == 130 { - "VerifiedGroup" - } else { - "Single" - } +fn chat_prefix(chat: &Chat) -> &'static str { + chat.typ.into() } pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::Error> { let chat_id = *context.cmdline_sel_chat_id.read().unwrap(); let mut sel_chat = if chat_id > 0 { - dc_get_chat(context, chat_id) + Chat::load_from_db(context, chat_id).ok() } else { - std::ptr::null_mut() + None }; let mut args = line.splitn(3, ' '); @@ -612,23 +606,21 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E ); for i in (0..cnt).rev() { - let chat = dc_get_chat(context, chatlist.get_chat_id(i)); - let temp_subtitle = dc_chat_get_subtitle(chat); - let temp_name = dc_chat_get_name(chat); + let chat = Chat::load_from_db(context, chatlist.get_chat_id(i))?; + let temp_subtitle = chat.get_subtitle(); + let temp_name = chat.get_name(); info!( context, 0, "{}#{}: {} [{}] [{} fresh]", - chat_prefix(chat), - dc_chat_get_id(chat) as libc::c_int, - as_str(temp_name), - as_str(temp_subtitle), - dc_get_fresh_msg_cnt(context, dc_chat_get_id(chat)) as libc::c_int, + chat_prefix(&chat), + chat.get_id(), + temp_name, + temp_subtitle, + chat::get_fresh_msg_cnt(context, chat.get_id()), ); - free(temp_subtitle as *mut libc::c_void); - free(temp_name as *mut libc::c_void); - let lot = chatlist.get_summary(i, chat); - let statestr = if 0 != dc_chat_get_archived(chat) { + let lot = chatlist.get_summary(i, Some(&chat)); + let statestr = if chat.is_archived() { " [Archived]" } else { match dc_lot_get_state(lot) { @@ -651,7 +643,7 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E to_string(text2), statestr, ×tr, - if 0 != dc_chat_is_sending_locations(chat) { + if chat.is_sending_locations() { "📍" } else { "" @@ -660,7 +652,6 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E free(text1 as *mut libc::c_void); free(text2 as *mut libc::c_void); dc_lot_unref(lot); - dc_chat_unref(chat); info!( context, 0, "================================================================================" @@ -673,109 +664,86 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E println!("{} chats", cnt); } "chat" => { - if sel_chat.is_null() && arg1.is_empty() { + if sel_chat.is_none() && arg1.is_empty() { bail!("Argument [chat-id] is missing."); } - if !sel_chat.is_null() && !arg1.is_empty() { - dc_chat_unref(sel_chat); - } if !arg1.is_empty() { let chat_id = arg1.parse()?; println!("Selecting chat #{}", chat_id); - sel_chat = dc_get_chat(context, chat_id); + sel_chat = Some(Chat::load_from_db(context, chat_id)?); *context.cmdline_sel_chat_id.write().unwrap() = chat_id; } - ensure!(!sel_chat.is_null(), "Failed to select chat"); + ensure!(sel_chat.is_some(), "Failed to select chat"); + let sel_chat = sel_chat.as_ref().unwrap(); - let msglist = dc_get_chat_msgs(context, dc_chat_get_id(sel_chat), 0x1, 0); - let temp2 = dc_chat_get_subtitle(sel_chat); - let temp_name = dc_chat_get_name(sel_chat); + let msglist = chat::get_chat_msgs(context, sel_chat.get_id(), 0x1, 0); + let temp2 = sel_chat.get_subtitle(); + let temp_name = sel_chat.get_name(); info!( context, 0, "{}#{}: {} [{}]{}", chat_prefix(sel_chat), - dc_chat_get_id(sel_chat), - as_str(temp_name), - as_str(temp2), - if 0 != dc_chat_is_sending_locations(sel_chat) { + sel_chat.get_id(), + temp_name, + temp2, + if sel_chat.is_sending_locations() { "📍" } else { "" }, ); - free(temp_name as *mut libc::c_void); - free(temp2 as *mut libc::c_void); if !msglist.is_null() { log_msglist(context, msglist); dc_array_unref(msglist); } - let draft = dc_get_draft(context, dc_chat_get_id(sel_chat)); + let draft = chat::get_draft(context, sel_chat.get_id()); if !draft.is_null() { log_msg(context, "Draft", draft); dc_msg_unref(draft); } println!( "{} messages.", - dc_get_msg_cnt(context, dc_chat_get_id(sel_chat)) + chat::get_msg_cnt(context, sel_chat.get_id()) ); - dc_marknoticed_chat(context, dc_chat_get_id(sel_chat)); + chat::marknoticed_chat(context, sel_chat.get_id())?; } "createchat" => { ensure!(!arg1.is_empty(), "Argument missing."); let contact_id: libc::c_int = arg1.parse()?; - let chat_id: libc::c_int = - dc_create_chat_by_contact_id(context, contact_id as uint32_t) as libc::c_int; - if chat_id != 0 { - println!("Single#{} created successfully.", chat_id,); - } else { - bail!("Failed to create chat"); - } + let chat_id = chat::create_by_contact_id(context, contact_id as uint32_t)?; + + println!("Single#{} created successfully.", chat_id,); } "createchatbymsg" => { ensure!(!arg1.is_empty(), "Argument missing"); - let msg_id_0: libc::c_int = arg1.parse()?; - let chat_id_0: libc::c_int = - dc_create_chat_by_msg_id(context, msg_id_0 as uint32_t) as libc::c_int; - if chat_id_0 != 0 { - let chat_0: *mut Chat = dc_get_chat(context, chat_id_0 as uint32_t); - println!( - "{}#{} created successfully.", - chat_prefix(chat_0), - chat_id_0, - ); - dc_chat_unref(chat_0); - } else { - bail!(""); - } + let msg_id: u32 = arg1.parse()?; + let chat_id = chat::create_by_msg_id(context, msg_id)?; + let chat = Chat::load_from_db(context, chat_id)?; + + println!("{}#{} created successfully.", chat_prefix(&chat), chat_id,); } "creategroup" => { ensure!(!arg1.is_empty(), "Argument missing."); - let chat_id_1: libc::c_int = dc_create_group_chat(context, 0, arg1_c) as libc::c_int; - if chat_id_1 != 0 { - println!("Group#{} created successfully.", chat_id_1,); - } else { - bail!("Failed to create group"); - } + let chat_id = chat::create_group_chat(context, VerifiedStatus::Unverified, arg1)?; + + println!("Group#{} created successfully.", chat_id); } "createverified" => { ensure!(!arg1.is_empty(), "Argument missing."); - let chat_id_2: libc::c_int = dc_create_group_chat(context, 1, arg1_c) as libc::c_int; - if chat_id_2 != 0 { - println!("VerifiedGroup#{} created successfully.", chat_id_2,); - } else { - bail!("Failed to create verified group"); - } + let chat_id = chat::create_group_chat(context, VerifiedStatus::Verified, arg1)?; + + println!("VerifiedGroup#{} created successfully.", chat_id); } "addmember" => { - ensure!(!sel_chat.is_null(), "No chat selected"); + ensure!(sel_chat.is_some(), "No chat selected"); ensure!(!arg1.is_empty(), "Argument missing."); let contact_id_0: libc::c_int = arg1.parse()?; - if 0 != dc_add_contact_to_chat( + if 0 != chat::add_contact_to_chat( context, - dc_chat_get_id(sel_chat), + sel_chat.as_ref().unwrap().get_id(), contact_id_0 as uint32_t, ) { println!("Contact added to chat."); @@ -784,62 +752,56 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E } } "removemember" => { - ensure!(!sel_chat.is_null(), "No chat selected."); + ensure!(sel_chat.is_some(), "No chat selected."); ensure!(!arg1.is_empty(), "Argument missing."); let contact_id_1: libc::c_int = arg1.parse()?; - if 0 != dc_remove_contact_from_chat( + chat::remove_contact_from_chat( context, - dc_chat_get_id(sel_chat), + sel_chat.as_ref().unwrap().get_id(), contact_id_1 as uint32_t, - ) { - println!("Contact added to chat."); - } else { - bail!("Cannot remove member from chat."); - } + )?; + + println!("Contact added to chat."); } "groupname" => { - ensure!(!sel_chat.is_null(), "No chat selected."); + ensure!(sel_chat.is_some(), "No chat selected."); ensure!(!arg1.is_empty(), "Argument missing."); - if 0 != dc_set_chat_name(context, dc_chat_get_id(sel_chat), arg1_c) { - println!("Chat name set"); - } else { - bail!("Failed to set chat name"); - } + chat::set_chat_name(context, sel_chat.as_ref().unwrap().get_id(), arg1)?; + + println!("Chat name set"); } "groupimage" => { - ensure!(!sel_chat.is_null(), "No chat selected."); + ensure!(sel_chat.is_some(), "No chat selected."); ensure!(!arg1.is_empty(), "Argument missing."); - if 0 != dc_set_chat_profile_image( - context, - dc_chat_get_id(sel_chat), - if !arg1.is_empty() { - arg1_c - } else { - std::ptr::null_mut() - }, - ) { - println!("Chat image set"); - } else { - bail!("Failed to set chat image"); - } + chat::set_chat_profile_image(context, sel_chat.as_ref().unwrap().get_id(), arg1)?; + + println!("Chat image set"); } "chatinfo" => { - ensure!(!sel_chat.is_null(), "No chat selected."); + ensure!(sel_chat.is_some(), "No chat selected."); - let contacts = dc_get_chat_contacts(context, dc_chat_get_id(sel_chat)); + let contacts = chat::get_chat_contacts(context, sel_chat.as_ref().unwrap().get_id()); info!(context, 0, "Memberlist:"); log_contactlist(context, &contacts); println!( "{} contacts\nLocation streaming: {}", contacts.len(), - dc_is_sending_locations_to_chat(context, dc_chat_get_id(sel_chat)), + dc_is_sending_locations_to_chat(context, sel_chat.as_ref().unwrap().get_id()), ); } "getlocations" => { + ensure!(sel_chat.is_some(), "No chat selected."); + let contact_id = arg1.parse().unwrap_or_default(); - let locations = dc_get_locations(context, dc_chat_get_id(sel_chat), contact_id, 0, 0); + let locations = dc_get_locations( + context, + sel_chat.as_ref().unwrap().get_id(), + contact_id, + 0, + 0, + ); let default_marker = "-".to_string(); for location in &locations { let marker = location.marker.as_ref().unwrap_or(&default_marker); @@ -863,12 +825,16 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E } } "sendlocations" => { - ensure!(!sel_chat.is_null(), "No chat selected."); + ensure!(sel_chat.is_some(), "No chat selected."); ensure!(!arg1.is_empty(), "No timeout given."); let seconds = arg1.parse()?; - dc_send_locations_to_chat(context, dc_chat_get_id(sel_chat), seconds); - println!("Locations will be sent to Chat#{} for {} seconds. Use 'setlocation ' to play around.", dc_chat_get_id(sel_chat), seconds); + dc_send_locations_to_chat(context, sel_chat.as_ref().unwrap().get_id(), seconds); + println!( + "Locations will be sent to Chat#{} for {} seconds. Use 'setlocation ' to play around.", + sel_chat.as_ref().unwrap().get_id(), + seconds + ); } "setlocation" => { ensure!( @@ -889,27 +855,19 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E dc_delete_all_locations(context); } "send" => { - ensure!(!sel_chat.is_null(), "No chat selected."); + ensure!(sel_chat.is_some(), "No chat selected."); ensure!(!arg1.is_empty(), "No message text given."); let msg = format!("{} {}", arg1, arg2); - if 0 != dc_send_text_msg(context, dc_chat_get_id(sel_chat), msg) { - println!("Message sent."); - } else { - bail!("Sending failed."); - } + chat::send_text_msg(context, sel_chat.as_ref().unwrap().get_id(), msg)?; } "sendempty" => { - ensure!(!sel_chat.is_null(), "No chat selected."); - if 0 != dc_send_text_msg(context, dc_chat_get_id(sel_chat), "".into()) { - println!("Message sent."); - } else { - bail!("Sending failed."); - } + ensure!(sel_chat.is_some(), "No chat selected."); + chat::send_text_msg(context, sel_chat.as_ref().unwrap().get_id(), "".into())?; } "sendimage" | "sendfile" => { - ensure!(!sel_chat.is_null(), "No chat selected."); + ensure!(sel_chat.is_some(), "No chat selected."); ensure!(!arg1.is_empty() && !arg2.is_empty(), "No file given."); let msg_0 = dc_msg_new( @@ -922,14 +880,14 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E ); dc_msg_set_file(msg_0, arg1_c, 0 as *const libc::c_char); dc_msg_set_text(msg_0, arg2_c); - dc_send_msg(context, dc_chat_get_id(sel_chat), msg_0); + chat::send_msg(context, sel_chat.as_ref().unwrap().get_id(), msg_0)?; dc_msg_unref(msg_0); } "listmsgs" => { ensure!(!arg1.is_empty(), "Argument missing."); - let chat = if !sel_chat.is_null() { - dc_chat_get_id(sel_chat) + let chat = if let Some(ref sel_chat) = sel_chat { + sel_chat.get_id() } else { 0 as libc::c_uint }; @@ -943,25 +901,29 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E } } "draft" => { - ensure!(!sel_chat.is_null(), "No chat selected."); + ensure!(sel_chat.is_some(), "No chat selected."); if !arg1.is_empty() { let draft_0 = dc_msg_new(context, Viewtype::Text); dc_msg_set_text(draft_0, arg1_c); - dc_set_draft(context, dc_chat_get_id(sel_chat), draft_0); + chat::set_draft(context, sel_chat.as_ref().unwrap().get_id(), draft_0); dc_msg_unref(draft_0); println!("Draft saved."); } else { - dc_set_draft(context, dc_chat_get_id(sel_chat), 0 as *mut dc_msg_t); + chat::set_draft( + context, + sel_chat.as_ref().unwrap().get_id(), + 0 as *mut dc_msg_t, + ); println!("Draft deleted."); } } "listmedia" => { - ensure!(!sel_chat.is_null(), "No chat selected."); + ensure!(sel_chat.is_some(), "No chat selected."); - let images = dc_get_chat_media( + let images = chat::get_chat_media( context, - dc_chat_get_id(sel_chat), + sel_chat.as_ref().unwrap().get_id(), Viewtype::Image, Viewtype::Gif, Viewtype::Video, @@ -982,12 +944,16 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E "archive" | "unarchive" => { ensure!(!arg1.is_empty(), "Argument missing."); let chat_id = arg1.parse()?; - dc_archive_chat(context, chat_id, if arg0 == "archive" { 1 } else { 0 }); + chat::archive( + context, + chat_id, + if arg0 == "archive" { true } else { false }, + )?; } "delchat" => { ensure!(!arg1.is_empty(), "Argument missing."); let chat_id = arg1.parse()?; - dc_delete_chat(context, chat_id); + chat::delete(context, chat_id)?; } "msginfo" => { ensure!(!arg1.is_empty(), "Argument missing."); @@ -1012,7 +978,7 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E let mut msg_ids = [0; 1]; let chat_id = arg2.parse()?; msg_ids[0] = arg1.parse()?; - dc_forward_msgs(context, msg_ids.as_mut_ptr(), 1, chat_id); + chat::forward_msgs(context, msg_ids.as_mut_ptr(), 1, chat_id); } "markseen" => { ensure!(!arg1.is_empty(), "Argument missing."); @@ -1082,9 +1048,8 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E if 0 != i { res += ", "; } - let chat = dc_get_chat(context, chatlist.get_chat_id(i)); - res += &format!("{}#{}", chat_prefix(chat), dc_chat_get_id(chat)); - dc_chat_unref(chat); + let chat = Chat::load_from_db(context, chatlist.get_chat_id(i))?; + res += &format!("{}#{}", chat_prefix(&chat), chat.get_id()); } } @@ -1130,10 +1095,6 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E _ => bail!("Unknown command: \"{}\" type ? for help.", arg0), } - if !sel_chat.is_null() { - dc_chat_unref(sel_chat); - } - free(arg1_c as *mut _); free(arg2_c as *mut _); diff --git a/examples/simple.rs b/examples/simple.rs index 628414c93..2a52d6041 100644 --- a/examples/simple.rs +++ b/examples/simple.rs @@ -5,12 +5,12 @@ use std::sync::{Arc, RwLock}; use std::{thread, time}; use tempfile::tempdir; +use deltachat::chat; use deltachat::chatlist::*; use deltachat::config; use deltachat::constants::Event; use deltachat::contact::*; use deltachat::context::*; -use deltachat::dc_chat::*; use deltachat::dc_configure::*; use deltachat::dc_job::{ dc_perform_imap_fetch, dc_perform_imap_idle, dc_perform_imap_jobs, dc_perform_smtp_idle, @@ -96,14 +96,14 @@ fn main() { println!("sending a message"); let contact_id = Contact::create(&ctx, "dignifiedquire", "dignifiedquire@gmail.com").unwrap(); - let chat_id = dc_create_chat_by_contact_id(&ctx, contact_id); - dc_send_text_msg(&ctx, chat_id, "Hi, here is my first message!".into()); + let chat_id = chat::create_by_contact_id(&ctx, contact_id).unwrap(); + chat::send_text_msg(&ctx, chat_id, "Hi, here is my first message!".into()).unwrap(); println!("fetching chats.."); let chats = Chatlist::try_load(&ctx, 0, None, None).unwrap(); for i in 0..chats.len() { - let summary = chats.get_summary(0, std::ptr::null_mut()); + let summary = chats.get_summary(0, None); let text1 = dc_lot_get_text1(summary); let text2 = dc_lot_get_text2(summary); diff --git a/src/chat.rs b/src/chat.rs new file mode 100644 index 000000000..bdf3da164 --- /dev/null +++ b/src/chat.rs @@ -0,0 +1,2060 @@ +use std::ffi::CString; +use std::path::Path; + +use crate::chatlist::*; +use crate::constants::*; +use crate::contact::*; +use crate::context::Context; +use crate::dc_array::*; +use crate::dc_job::*; +use crate::dc_msg::*; +use crate::dc_tools::*; +use crate::error::Error; +use crate::param::*; +use crate::sql::{self, Sql}; +use crate::stock::StockMessage; +use crate::types::*; +use crate::x::*; +use std::ptr; + +/// An object representing a single chat in memory. +/// Chat objects are created using eg. `Chat::load_from_db` +/// and are not updated on database changes; +/// if you want an update, you have to recreate the object. +#[derive(Clone)] +pub struct Chat<'a> { + pub context: &'a Context, + pub id: u32, + pub typ: Chattype, + pub name: String, + archived: bool, + pub grpid: String, + blocked: Blocked, + pub param: Params, + pub gossiped_timestamp: i64, + is_sending_locations: bool, +} + +impl<'a> Chat<'a> { + pub fn load_from_db(context: &'a Context, chat_id: u32) -> Result { + let res = context.sql.query_row( + "SELECT c.id,c.type,c.name, c.grpid,c.param,c.archived, \ + c.blocked, c.gossiped_timestamp, c.locations_send_until \ + FROM chats c WHERE c.id=?;", + params![chat_id as i32], + |row| { + let c = Chat { + context, + id: row.get(0)?, + typ: row.get(1)?, + name: row.get::<_, String>(2)?, + grpid: row.get::<_, String>(3)?, + param: row.get::<_, String>(4)?.parse().unwrap_or_default(), + archived: row.get(5)?, + blocked: row.get::<_, Option<_>>(6)?.unwrap_or_default(), + gossiped_timestamp: row.get(7)?, + is_sending_locations: row.get(8)?, + }; + + Ok(c) + }, + ); + + match res { + Err(err @ crate::error::Error::Sql(rusqlite::Error::QueryReturnedNoRows)) => Err(err), + Err(err) => match err { + _ => { + error!( + context, + 0, "chat: failed to load from db {}: {:?}", chat_id, err + ); + Err(err) + } + }, + Ok(mut chat) => { + match chat.id { + 1 => { + chat.name = chat.context.stock_str(StockMessage::DeadDrop).into(); + } + 6 => { + let tempname = chat.context.stock_str(StockMessage::ArchivedChats); + let cnt = dc_get_archived_cnt(chat.context); + chat.name = format!("{} ({})", tempname, cnt); + } + 5 => { + chat.name = chat.context.stock_str(StockMessage::StarredMsgs).into(); + } + _ => { + if chat.typ == Chattype::Single { + let contacts = get_chat_contacts(chat.context, chat.id); + let mut chat_name = "Err [Name not found]".to_owned(); + + if !(*contacts).is_empty() { + if let Ok(contact) = Contact::get_by_id(chat.context, contacts[0]) { + chat_name = contact.get_display_name().to_owned(); + } + } + + chat.name = chat_name; + } + + if chat.param.exists(Param::Selftalk) { + chat.name = chat.context.stock_str(StockMessage::SelfMsg).into(); + } + } + } + Ok(chat) + } + } + } + + pub fn is_self_talk(&self) -> bool { + self.param.exists(Param::Selftalk) + } + + pub fn update_param(&mut self) -> Result<(), Error> { + sql::execute( + self.context, + &self.context.sql, + "UPDATE chats SET param=? WHERE id=?", + params![self.param.to_string(), self.id as i32], + ) + } + + pub fn get_id(&self) -> u32 { + self.id + } + + pub fn get_type(&self) -> Chattype { + self.typ + } + + pub fn get_name(&self) -> &str { + &self.name + } + + pub fn get_subtitle(&self) -> String { + // returns either the address or the number of chat members + + if self.typ == Chattype::Single && self.param.exists(Param::Selftalk) { + return self + .context + .stock_str(StockMessage::SelfTalkSubTitle) + .into(); + } + + if self.typ == Chattype::Single { + return self + .context + .sql + .query_row_col( + self.context, + "SELECT c.addr FROM chats_contacts cc \ + LEFT JOIN contacts c ON c.id=cc.contact_id \ + WHERE cc.chat_id=?;", + params![self.id as i32], + 0, + ) + .unwrap_or_else(|| "Err".into()); + } + + if self.typ == Chattype::Group || self.typ == Chattype::VerifiedGroup { + if self.id == 1 { + return self.context.stock_str(StockMessage::DeadDrop).into(); + } + let cnt = get_chat_contact_cnt(self.context, self.id); + return self + .context + .stock_string_repl_int(StockMessage::Member, cnt) + .into(); + } + + return "Err".into(); + } + + unsafe fn get_parent_mime_headers( + &self, + parent_rfc724_mid: *mut *mut libc::c_char, + parent_in_reply_to: *mut *mut libc::c_char, + parent_references: *mut *mut libc::c_char, + ) -> Result<(), Error> { + if !(parent_rfc724_mid.is_null() + || parent_in_reply_to.is_null() + || parent_references.is_null()) + { + // prefer a last message that isn't from us + let next = self + .context + .sql + .query_row( + "SELECT rfc724_mid, mime_in_reply_to, mime_references \ + FROM msgs WHERE chat_id=?1 AND timestamp=(SELECT max(timestamp) \ + FROM msgs WHERE chat_id=?1 AND from_id!=?2);", + params![self.id as i32, DC_CONTACT_ID_SELF as i32], + |row| { + *parent_rfc724_mid = row.get::<_, String>(0)?.strdup(); + *parent_in_reply_to = row.get::<_, String>(1)?.strdup(); + *parent_references = row.get::<_, String>(2)?.strdup(); + Ok(()) + }, + ) + .is_ok(); + + if !next { + self.context.sql.query_row( + "SELECT rfc724_mid, mime_in_reply_to, mime_references \ + FROM msgs WHERE chat_id=?1 AND timestamp=(SELECT min(timestamp) \ + FROM msgs WHERE chat_id=?1 AND from_id==?2);", + params![self.id as i32, DC_CONTACT_ID_SELF as i32], + |row| { + *parent_rfc724_mid = row.get::<_, String>(0)?.strdup(); + *parent_in_reply_to = row.get::<_, String>(1)?.strdup(); + *parent_references = row.get::<_, String>(2)?.strdup(); + Ok(()) + }, + )?; + } + } + + Ok(()) + } + + pub unsafe fn get_profile_image(&self) -> Option { + if let Some(image_rel) = self.param.get(Param::ProfileImage) { + if !image_rel.is_empty() { + return Some(to_string(dc_get_abs_path(self.context, image_rel))); + } + } else if self.typ == Chattype::Single { + let contacts = get_chat_contacts(self.context, self.id); + if !contacts.is_empty() { + if let Ok(contact) = Contact::get_by_id(self.context, contacts[0]) { + return contact.get_profile_image(); + } + } + } + + None + } + + pub fn get_color(&self) -> u32 { + let mut color = 0; + + if self.typ == Chattype::Single { + let contacts = get_chat_contacts(self.context, self.id); + if !contacts.is_empty() { + if let Ok(contact) = Contact::get_by_id(self.context, contacts[0]) { + color = contact.get_color(); + } + } + } else { + color = dc_str_to_color(&self.name); + } + + color + } + + pub fn is_archived(&self) -> bool { + self.archived + } + + pub fn is_unpromoted(&self) -> bool { + self.param.get_int(Param::Unpromoted).unwrap_or_default() == 1 + } + + pub fn is_verified(&self) -> bool { + (self.typ == Chattype::VerifiedGroup) + } + + pub fn is_sending_locations(&self) -> bool { + self.is_sending_locations + } + + #[allow(non_snake_case)] + unsafe fn prepare_msg_raw( + &mut self, + context: &Context, + msg: *mut dc_msg_t, + timestamp: i64, + ) -> Result { + let mut do_guarantee_e2ee: libc::c_int; + let e2ee_enabled: libc::c_int; + let mut OK_TO_CONTINUE = true; + let mut parent_rfc724_mid = ptr::null_mut(); + let mut parent_references = ptr::null_mut(); + let mut parent_in_reply_to = ptr::null_mut(); + let mut new_rfc724_mid = ptr::null_mut(); + let mut new_references = ptr::null_mut(); + let mut new_in_reply_to = ptr::null_mut(); + let mut msg_id = 0; + let mut to_id = 0; + let mut location_id = 0; + + if !(self.typ == Chattype::Single + || self.typ == Chattype::Group + || self.typ == Chattype::VerifiedGroup) + { + error!(context, 0, "Cannot send to chat type #{}.", self.typ,); + } else if (self.typ == Chattype::Group || self.typ == Chattype::VerifiedGroup) + && 0 == is_contact_in_chat(context, self.id, 1 as u32) + { + log_event!( + context, + Event::ERROR_SELF_NOT_IN_GROUP, + 0, + "Cannot send message; self not in group.", + ); + } else { + let from = context.sql.get_config(context, "configured_addr"); + if from.is_none() { + error!(context, 0, "Cannot send message, not configured.",); + } else { + let from_c = CString::yolo(from.unwrap()); + new_rfc724_mid = dc_create_outgoing_rfc724_mid( + if self.typ == Chattype::Group || self.typ == Chattype::VerifiedGroup { + self.grpid.strdup() + } else { + ptr::null_mut() + }, + from_c.as_ptr(), + ); + + if self.typ == Chattype::Single { + if let Some(id) = context.sql.query_row_col( + context, + "SELECT contact_id FROM chats_contacts WHERE chat_id=?;", + params![self.id as i32], + 0, + ) { + to_id = id; + } else { + error!( + context, + 0, "Cannot send message, contact for chat #{} not found.", self.id, + ); + OK_TO_CONTINUE = false; + } + } else { + if self.typ == Chattype::Group || self.typ == Chattype::VerifiedGroup { + if self.param.get_int(Param::Unpromoted).unwrap_or_default() == 1 { + self.param.remove(Param::Unpromoted); + self.update_param().unwrap(); + } + } + } + if OK_TO_CONTINUE { + /* check if we can guarantee E2EE for this message. + if we guarantee E2EE, and circumstances change + so that E2EE is no longer available at a later point (reset, changed settings), + we do not send the message out at all */ + do_guarantee_e2ee = 0; + e2ee_enabled = context + .sql + .get_config_int(context, "e2ee_enabled") + .unwrap_or_else(|| 1); + if 0 != e2ee_enabled + && (*msg) + .param + .get_int(Param::ForcePlaintext) + .unwrap_or_default() + == 0 + { + let mut can_encrypt = 1; + let mut all_mutual = 1; + + let res = context.sql.query_row( + "SELECT ps.prefer_encrypted, c.addr \ + FROM chats_contacts cc \ + LEFT JOIN contacts c ON cc.contact_id=c.id \ + LEFT JOIN acpeerstates ps ON c.addr=ps.addr \ + WHERE cc.chat_id=? AND cc.contact_id>9;", + params![self.id], + |row| { + let state: String = row.get(1)?; + + if let Some(prefer_encrypted) = row.get::<_, Option>(0)? { + if prefer_encrypted != 1 { + info!( + context, + 0, + "[autocrypt] peerstate for {} is {}", + state, + if prefer_encrypted == 0 { + "NOPREFERENCE" + } else { + "RESET" + }, + ); + all_mutual = 0; + } + } else { + info!(context, 0, "[autocrypt] no peerstate for {}", state,); + can_encrypt = 0; + all_mutual = 0; + } + Ok(()) + }, + ); + match res { + Ok(_) => {} + Err(err) => { + warn!(context, 0, "chat: failed to load peerstates: {:?}", err); + } + } + + if 0 != can_encrypt { + if 0 != all_mutual { + do_guarantee_e2ee = 1; + } else if last_msg_in_chat_encrypted(context, &context.sql, self.id) { + do_guarantee_e2ee = 1; + } + } + } + if 0 != do_guarantee_e2ee { + (*msg).param.set_int(Param::GuranteeE2ee, 1); + } + (*msg).param.remove(Param::ErroneousE2ee); + if !self.is_self_talk() + && self + .get_parent_mime_headers( + &mut parent_rfc724_mid, + &mut parent_in_reply_to, + &mut parent_references, + ) + .is_ok() + { + if !parent_rfc724_mid.is_null() + && 0 != *parent_rfc724_mid.offset(0isize) as libc::c_int + { + new_in_reply_to = dc_strdup(parent_rfc724_mid) + } + if !parent_references.is_null() { + let space: *mut libc::c_char; + space = strchr(parent_references, ' ' as i32); + if !space.is_null() { + *space = 0 as libc::c_char + } + } + if !parent_references.is_null() + && 0 != *parent_references.offset(0isize) as libc::c_int + && !parent_rfc724_mid.is_null() + && 0 != *parent_rfc724_mid.offset(0isize) as libc::c_int + { + new_references = dc_mprintf( + b"%s %s\x00" as *const u8 as *const libc::c_char, + parent_references, + parent_rfc724_mid, + ) + } else if !parent_references.is_null() + && 0 != *parent_references.offset(0isize) as libc::c_int + { + new_references = dc_strdup(parent_references) + } else if !parent_in_reply_to.is_null() + && 0 != *parent_in_reply_to.offset(0isize) as libc::c_int + && !parent_rfc724_mid.is_null() + && 0 != *parent_rfc724_mid.offset(0isize) as libc::c_int + { + new_references = dc_mprintf( + b"%s %s\x00" as *const u8 as *const libc::c_char, + parent_in_reply_to, + parent_rfc724_mid, + ) + } else if !parent_in_reply_to.is_null() + && 0 != *parent_in_reply_to.offset(0isize) as libc::c_int + { + new_references = dc_strdup(parent_in_reply_to) + } + } + + // add independent location to database + + if (*msg).param.exists(Param::SetLatitude) { + if sql::execute( + context, + &context.sql, + "INSERT INTO locations \ + (timestamp,from_id,chat_id, latitude,longitude,independent)\ + VALUES (?,?,?, ?,?,1);", + params![ + timestamp, + DC_CONTACT_ID_SELF as i32, + self.id as i32, + (*msg) + .param + .get_float(Param::SetLatitude) + .unwrap_or_default(), + (*msg) + .param + .get_float(Param::SetLongitude) + .unwrap_or_default(), + ], + ) + .is_ok() + { + location_id = sql::get_rowid2( + context, + &context.sql, + "locations", + "timestamp", + timestamp, + "from_id", + DC_CONTACT_ID_SELF as i32, + ); + } + } + + // add message to the database + + if sql::execute( + context, + &context.sql, + "INSERT INTO msgs (rfc724_mid, chat_id, from_id, to_id, timestamp, type, state, txt, param, hidden, mime_in_reply_to, mime_references, location_id) VALUES (?,?,?,?,?, ?,?,?,?,?, ?,?,?);", + params![ + as_str(new_rfc724_mid), + self.id as i32, + 1i32, + to_id as i32, + timestamp, + (*msg).type_0, + (*msg).state, + (*msg).text, + (*msg).param.to_string(), + (*msg).hidden, + to_string(new_in_reply_to), + to_string(new_references), + location_id as i32, + ] + ).is_ok() { + msg_id = sql::get_rowid( + context, + &context.sql, + "msgs", + "rfc724_mid", + as_str(new_rfc724_mid), + ); + } else { + error!( + context, + 0, + "Cannot send message, cannot insert to database (chat #{}).", + self.id, + ); + } + } + } + } + + free(parent_rfc724_mid as *mut libc::c_void); + free(parent_in_reply_to as *mut libc::c_void); + free(parent_references as *mut libc::c_void); + free(new_rfc724_mid as *mut libc::c_void); + free(new_in_reply_to as *mut libc::c_void); + free(new_references as *mut libc::c_void); + + Ok(msg_id) + } +} + +/// Create a normal chat or a group chat by a messages ID that comes typically +/// from the deaddrop, DC_CHAT_ID_DEADDROP (1). +/// +/// If the given message ID already belongs to a normal chat or to a group chat, +/// the chat ID of this chat is returned and no new chat is created. +/// If a new chat is created, the given message ID is moved to this chat, however, +/// there may be more messages moved to the chat from the deaddrop. To get the +/// chat messages, use dc_get_chat_msgs(). +/// +/// If the user is asked before creation, he should be +/// asked whether he wants to chat with the _contact_ belonging to the message; +/// the group names may be really weird when taken from the subject of implicit +/// groups and this may look confusing. +/// +/// Moreover, this function also scales up the origin of the contact belonging +/// to the message and, depending on the contacts origin, messages from the +/// same group may be shown or not - so, all in all, it is fine to show the +/// contact name only. +pub fn create_by_msg_id(context: &Context, msg_id: u32) -> Result { + let mut chat_id = 0; + let mut send_event = false; + let msg = unsafe { dc_msg_new_untyped(context) }; + + if dc_msg_load_from_db(msg, context, msg_id) { + if let Ok(chat) = Chat::load_from_db(context, unsafe { (*msg).chat_id }) { + if chat.id > DC_CHAT_ID_LAST_SPECIAL as u32 { + chat_id = chat.id; + if chat.blocked != Blocked::Not { + unblock(context, chat.id); + send_event = true; + } + Contact::scaleup_origin_by_id( + context, + unsafe { (*msg).from_id }, + Origin::CreateChat, + ); + } + } + } + + unsafe { dc_msg_unref(msg) }; + + if send_event { + context.call_cb(Event::MSGS_CHANGED, 0, 0); + } + + ensure!(chat_id > 0, "failed to load create chat"); + + Ok(chat_id) +} + +/// Create a normal chat with a single user. To create group chats, +/// see dc_create_group_chat(). +/// +/// If a chat already exists, this ID is returned, otherwise a new chat is created; +/// this new chat may already contain messages, eg. from the deaddrop, to get the +/// chat messages, use dc_get_chat_msgs(). +pub fn create_by_contact_id(context: &Context, contact_id: u32) -> Result { + let chat_id = match lookup_by_contact_id(context, contact_id) { + Ok((chat_id, chat_blocked)) => { + if chat_blocked != Blocked::Not { + // unblock chat (typically move it from the deaddrop to view + unblock(context, chat_id); + } + chat_id + } + Err(err) => { + if !Contact::real_exists_by_id(context, contact_id) + && contact_id != DC_CONTACT_ID_SELF as u32 + { + warn!( + context, + 0, "Cannot create chat, contact {} does not exist.", contact_id, + ); + return Err(err); + } else { + let (chat_id, _) = + create_or_lookup_by_contact_id(context, contact_id, Blocked::Not)?; + Contact::scaleup_origin_by_id(context, contact_id, Origin::CreateChat); + chat_id + } + } + }; + + context.call_cb(Event::MSGS_CHANGED, 0i32 as uintptr_t, 0i32 as uintptr_t); + + Ok(chat_id) +} + +pub fn unblock(context: &Context, chat_id: u32) { + set_blocking(context, chat_id, Blocked::Not); +} + +pub fn set_blocking(context: &Context, chat_id: u32, new_blocking: Blocked) -> bool { + sql::execute( + context, + &context.sql, + "UPDATE chats SET blocked=? WHERE id=?;", + params![new_blocking, chat_id as i32], + ) + .is_ok() +} + +pub fn create_or_lookup_by_contact_id( + context: &Context, + contact_id: u32, + create_blocked: Blocked, +) -> Result<(u32, Blocked), Error> { + ensure!(context.sql.is_open(), "Database not available"); + ensure!(contact_id > 0, "Invalid contact id requested"); + + if let Ok((chat_id, chat_blocked)) = lookup_by_contact_id(context, contact_id) { + // Already exists, no need to create. + return Ok((chat_id, chat_blocked)); + } + + let contact = Contact::load_from_db(context, contact_id)?; + let chat_name = contact.get_display_name(); + + sql::execute( + context, + &context.sql, + format!( + "INSERT INTO chats (type, name, param, blocked, grpid) VALUES({}, '{}', '{}', {}, '{}')", + 100, + chat_name, + if contact_id == DC_CONTACT_ID_SELF as u32 { "K=1" } else { "" }, + create_blocked as u8, + contact.get_addr(), + ), + params![], + )?; + + let chat_id = sql::get_rowid(context, &context.sql, "chats", "grpid", contact.get_addr()); + + sql::execute( + context, + &context.sql, + format!( + "INSERT INTO chats_contacts (chat_id, contact_id) VALUES({}, {})", + chat_id, contact_id + ), + params![], + )?; + + Ok((chat_id, create_blocked)) +} + +pub fn lookup_by_contact_id(context: &Context, contact_id: u32) -> Result<(u32, Blocked), Error> { + ensure!(context.sql.is_open(), "Database not available"); + + context.sql.query_row( + "SELECT c.id, c.blocked FROM chats c INNER JOIN chats_contacts j ON c.id=j.chat_id WHERE c.type=100 AND c.id>9 AND j.contact_id=?;", + params![contact_id as i32], + |row| Ok((row.get(0)?, row.get::<_, Option<_>>(1)?.unwrap_or_default())), + ) +} + +pub fn get_by_contact_id(context: &Context, contact_id: u32) -> Result { + let (chat_id, blocked) = lookup_by_contact_id(context, contact_id)?; + ensure_eq!(blocked, Blocked::Not, "Requested contact is blocked"); + + Ok(chat_id) +} + +pub fn prepare_msg<'a>( + context: &'a Context, + chat_id: u32, + mut msg: *mut dc_msg_t<'a>, +) -> Result { + ensure!(!msg.is_null(), "No message provided"); + ensure!( + chat_id > DC_CHAT_ID_LAST_SPECIAL as u32, + "Cannot prepare message for special chat" + ); + + unsafe { (*msg).state = DC_STATE_OUT_PREPARING }; + let msg_id = prepare_msg_common(context, chat_id, msg)?; + context.call_cb( + Event::MSGS_CHANGED, + unsafe { (*msg).chat_id as uintptr_t }, + unsafe { (*msg).id as uintptr_t }, + ); + + Ok(msg_id) +} + +pub fn msgtype_has_file(msgtype: Viewtype) -> bool { + match msgtype { + Viewtype::Image => true, + Viewtype::Gif => true, + Viewtype::Audio => true, + Viewtype::Voice => true, + Viewtype::Video => true, + Viewtype::File => true, + _ => false, + } +} + +fn prepare_msg_common<'a>( + context: &'a Context, + chat_id: u32, + msg: *mut dc_msg_t<'a>, +) -> Result { + ensure!(!msg.is_null(), "No message provided"); + + let msg = unsafe { &mut *msg }; + + msg.id = 0; + msg.context = context; + + if msg.type_0 == Viewtype::Text { + // the caller should check if the message text is empty + } else if msgtype_has_file(msg.type_0) { + let path_filename = msg.param.get(Param::File); + + ensure!( + path_filename.is_some(), + "Attachment missing for message of type #{}.", + msg.type_0 + ); + + let mut path_filename = path_filename.unwrap().to_string(); + + if msg.state == DC_STATE_OUT_PREPARING && !dc_is_blobdir_path(context, &path_filename) { + bail!("Files must be created in the blob-directory."); + } + + ensure!( + dc_make_rel_and_copy(context, &mut path_filename), + "Failed to copy" + ); + + msg.param.set(Param::File, &path_filename); + if msg.type_0 == Viewtype::File || msg.type_0 == Viewtype::Image { + // Correct the type, take care not to correct already very special + // formats as GIF or VOICE. + // + // Typical conversions: + // - from FILE to AUDIO/VIDEO/IMAGE + // - from FILE/IMAGE to GIF */ + if let Some((better_type, better_mime)) = + dc_msg_guess_msgtype_from_suffix(Path::new(&path_filename)) + { + msg.type_0 = better_type; + msg.param.set(Param::MimeType, better_mime); + } + } else if !msg.param.exists(Param::MimeType) { + if let Some((_, mime)) = dc_msg_guess_msgtype_from_suffix(Path::new(&path_filename)) { + msg.param.set(Param::MimeType, mime); + } + } + info!( + context, + 0, "Attaching \"{}\" for message type #{}.", &path_filename, msg.type_0 + ); + } else { + bail!("Cannot send messages of type #{}.", msg.type_0); + } + + unarchive(context, chat_id)?; + + let mut chat = Chat::load_from_db(context, chat_id)?; + if msg.state != DC_STATE_OUT_PREPARING { + msg.state = DC_STATE_OUT_PENDING; + } + + msg.id = unsafe { chat.prepare_msg_raw(context, msg, dc_create_smeared_timestamp(context))? }; + msg.chat_id = chat_id; + + Ok(msg.id) +} + +fn last_msg_in_chat_encrypted(context: &Context, sql: &Sql, chat_id: u32) -> bool { + let packed: Option = sql.query_row_col( + context, + "SELECT param \ + FROM msgs WHERE timestamp=(SELECT MAX(timestamp) FROM msgs WHERE chat_id=?) \ + ORDER BY id DESC;", + params![chat_id as i32], + 0, + ); + + if let Some(ref packed) = packed { + match packed.parse::() { + Ok(param) => param.exists(Param::GuranteeE2ee), + Err(err) => { + error!(context, 0, "invalid params stored: '{}', {:?}", packed, err); + false + } + } + } else { + false + } +} + +pub fn is_contact_in_chat(context: &Context, chat_id: u32, contact_id: u32) -> libc::c_int { + /* this function works for group and for normal chats, however, it is more useful for group chats. + DC_CONTACT_ID_SELF may be used to check, if the user itself is in a group chat (DC_CONTACT_ID_SELF is not added to normal chats) */ + + context + .sql + .exists( + "SELECT contact_id FROM chats_contacts WHERE chat_id=? AND contact_id=?;", + params![chat_id as i32, contact_id as i32], + ) + .unwrap_or_default() as libc::c_int +} + +// Should return Result +pub fn unarchive(context: &Context, chat_id: u32) -> Result<(), Error> { + sql::execute( + context, + &context.sql, + "UPDATE chats SET archived=0 WHERE id=?", + params![chat_id as i32], + ) +} + +/// Send a message defined by a dc_msg_t object to a chat. +/// +/// Sends the event #DC_EVENT_MSGS_CHANGED on succcess. +/// However, this does not imply, the message really reached the recipient - +/// sending may be delayed eg. due to network problems. However, from your +/// view, you're done with the message. Sooner or later it will find its way. +pub unsafe fn send_msg<'a>( + context: &'a Context, + chat_id: u32, + msg: *mut dc_msg_t<'a>, +) -> Result { + ensure!(!msg.is_null(), "Invalid message"); + + if (*msg).state != DC_STATE_OUT_PREPARING { + // automatically prepare normal messages + prepare_msg_common(context, chat_id, msg)?; + } else { + // update message state of separately prepared messages + ensure!( + chat_id == 0 || chat_id == (*msg).chat_id, + "Inconsistent chat ID" + ); + dc_update_msg_state(context, (*msg).id, DC_STATE_OUT_PENDING); + } + + ensure!( + dc_job_send_msg(context, (*msg).id) != 0, + "Failed to initiate send job" + ); + + context.call_cb( + Event::MSGS_CHANGED, + (*msg).chat_id as uintptr_t, + (*msg).id as uintptr_t, + ); + + if (*msg).param.exists(Param::SetLatitude) { + context.call_cb(Event::LOCATION_CHANGED, DC_CONTACT_ID_SELF, 0); + } + + if 0 == chat_id { + let forwards = (*msg).param.get(Param::PrepForwards); + if let Some(forwards) = forwards { + for forward in forwards.split(' ') { + let id: i32 = forward.parse().unwrap_or_default(); + if 0 == id { + // avoid hanging if user tampers with db + break; + } else { + let copy = dc_get_msg(context, id as u32); + if !copy.is_null() { + // TODO: handle cleanup and return early instead + send_msg(context, 0, copy).unwrap(); + } + dc_msg_unref(copy); + } + } + (*msg).param.remove(Param::PrepForwards); + dc_msg_save_param_to_disk(msg); + } + } + + Ok((*msg).id) +} + +pub unsafe fn send_text_msg( + context: &Context, + chat_id: u32, + text_to_send: String, +) -> Result { + ensure!( + chat_id > DC_CHAT_ID_LAST_SPECIAL as u32, + "bad chat_id = {} <= 9", + chat_id + ); + + let mut msg = dc_msg_new(context, Viewtype::Text); + (*msg).text = Some(text_to_send); + send_msg(context, chat_id, msg) +} + +pub unsafe fn set_draft(context: &Context, chat_id: u32, msg: *mut dc_msg_t) { + if chat_id <= DC_CHAT_ID_LAST_SPECIAL as u32 { + return; + } + if set_draft_raw(context, chat_id, msg) { + context.call_cb(Event::MSGS_CHANGED, chat_id as uintptr_t, 0i32 as uintptr_t); + }; +} + +#[allow(non_snake_case)] +unsafe fn set_draft_raw(context: &Context, chat_id: u32, msg: *mut dc_msg_t) -> bool { + let mut OK_TO_CONTINUE = true; + // similar to as dc_set_draft() but does not emit an event + let prev_draft_msg_id: u32; + let mut sth_changed = false; + + prev_draft_msg_id = get_draft_msg_id(context, chat_id); + if 0 != prev_draft_msg_id { + dc_delete_msg_from_db(context, prev_draft_msg_id); + sth_changed = true; + } + // save new draft + if !msg.is_null() { + if (*msg).type_0 == Viewtype::Text { + OK_TO_CONTINUE = (*msg).text.as_ref().map_or(false, |s| !s.is_empty()); + } else if msgtype_has_file((*msg).type_0) { + if let Some(path_filename) = (*msg).param.get(Param::File) { + let mut path_filename = path_filename.to_string(); + if 0 != dc_msg_is_increation(msg) && !dc_is_blobdir_path(context, &path_filename) { + OK_TO_CONTINUE = false; + } else if !dc_make_rel_and_copy(context, &mut path_filename) { + OK_TO_CONTINUE = false; + } else { + (*msg).param.set(Param::File, path_filename); + } + } + } else { + OK_TO_CONTINUE = false; + } + if OK_TO_CONTINUE { + if sql::execute( + context, + &context.sql, + "INSERT INTO msgs (chat_id, from_id, timestamp, type, state, txt, param, hidden) \ + VALUES (?,?,?, ?,?,?,?,?);", + params![ + chat_id as i32, + 1, + time(), + (*msg).type_0, + DC_STATE_OUT_DRAFT, + (*msg).text.as_ref().map(String::as_str).unwrap_or(""), + (*msg).param.to_string(), + 1, + ], + ) + .is_ok() + { + sth_changed = true; + } + } + } + + sth_changed +} + +fn get_draft_msg_id(context: &Context, chat_id: u32) -> u32 { + context + .sql + .query_row_col::<_, i32>( + context, + "SELECT id FROM msgs WHERE chat_id=? AND state=?;", + params![chat_id as i32, DC_STATE_OUT_DRAFT], + 0, + ) + .unwrap_or_default() as u32 +} + +pub unsafe fn get_draft(context: &Context, chat_id: u32) -> *mut dc_msg_t { + if chat_id <= DC_CHAT_ID_LAST_SPECIAL as u32 { + return ptr::null_mut(); + } + let draft_msg_id = get_draft_msg_id(context, chat_id); + if draft_msg_id == 0 { + return ptr::null_mut(); + } + let draft_msg = dc_msg_new_untyped(context); + if !dc_msg_load_from_db(draft_msg, context, draft_msg_id) { + dc_msg_unref(draft_msg); + return ptr::null_mut(); + } + + draft_msg +} + +pub fn get_chat_msgs( + context: &Context, + chat_id: u32, + flags: u32, + marker1before: u32, +) -> *mut dc_array_t { + let mut ret = Vec::new(); + + let mut last_day = 0; + let cnv_to_local = dc_gm2local_offset(); + + let process_row = |row: &rusqlite::Row| Ok((row.get::<_, i32>(0)?, row.get::<_, i64>(1)?)); + let process_rows = |rows: rusqlite::MappedRows<_>| { + for row in rows { + let (curr_id, ts) = row?; + if curr_id as u32 == marker1before { + ret.push(DC_MSG_ID_MARKER1 as u32); + } + if 0 != flags & 0x1 { + let curr_local_timestamp = ts + cnv_to_local; + let curr_day = (curr_local_timestamp / 86400) as libc::c_int; + if curr_day != last_day { + ret.push(DC_MSG_ID_LAST_SPECIAL as u32); + last_day = curr_day; + } + } + ret.push(curr_id as u32); + } + Ok(()) + }; + + let success = if chat_id == 1 { + let show_emails = context + .sql + .get_config_int(context, "show_emails") + .unwrap_or_default(); + context.sql.query_map( + "SELECT m.id, m.timestamp FROM msgs m \ + LEFT JOIN chats ON m.chat_id=chats.id \ + LEFT JOIN contacts ON m.from_id=contacts.id WHERE m.from_id!=1 \ + AND m.from_id!=2 \ + AND m.hidden=0 \ + AND chats.blocked=2 \ + AND contacts.blocked=0 \ + AND m.msgrmsg>=? \ + ORDER BY m.timestamp,m.id;", + params![if show_emails == 2 { 0 } else { 1 }], + process_row, + process_rows, + ) + } else if chat_id == 5 { + context.sql.query_map( + "SELECT m.id, m.timestamp FROM msgs m \ + LEFT JOIN contacts ct ON m.from_id=ct.id WHERE m.starred=1 \ + AND m.hidden=0 \ + AND ct.blocked=0 \ + ORDER BY m.timestamp,m.id;", + params![], + process_row, + process_rows, + ) + } else { + context.sql.query_map( + "SELECT m.id, m.timestamp FROM msgs m \ + WHERE m.chat_id=? \ + AND m.hidden=0 \ + ORDER BY m.timestamp,m.id;", + params![chat_id as i32], + process_row, + process_rows, + ) + }; + + if success.is_ok() { + dc_array_t::from(ret).into_raw() + } else { + 0 as *mut dc_array_t + } +} + +pub fn get_msg_cnt(context: &Context, chat_id: u32) -> usize { + context + .sql + .query_row_col::<_, i32>( + context, + "SELECT COUNT(*) FROM msgs WHERE chat_id=?;", + params![chat_id as i32], + 0, + ) + .unwrap_or_default() as usize +} + +pub fn get_fresh_msg_cnt(context: &Context, chat_id: u32) -> usize { + context + .sql + .query_row_col::<_, i32>( + context, + "SELECT COUNT(*) FROM msgs \ + WHERE state=10 \ + AND hidden=0 \ + AND chat_id=?;", + params![chat_id as i32], + 0, + ) + .unwrap_or_default() as usize +} + +pub fn marknoticed_chat(context: &Context, chat_id: u32) -> Result<(), Error> { + if !context.sql.exists( + "SELECT id FROM msgs WHERE chat_id=? AND state=?;", + params![chat_id as i32, DC_STATE_IN_FRESH], + )? { + return Ok(()); + } + + sql::execute( + context, + &context.sql, + "UPDATE msgs \ + SET state=13 WHERE chat_id=? AND state=10;", + params![chat_id as i32], + )?; + + context.call_cb(Event::MSGS_CHANGED, 0 as uintptr_t, 0 as uintptr_t); + + Ok(()) +} + +pub fn marknoticed_all_chats(context: &Context) -> Result<(), Error> { + if !context.sql.exists( + "SELECT id FROM msgs \ + WHERE state=10;", + params![], + )? { + return Ok(()); + } + + sql::execute( + context, + &context.sql, + "UPDATE msgs \ + SET state=13 WHERE state=10;", + params![], + )?; + + context.call_cb(Event::MSGS_CHANGED, 0 as uintptr_t, 0 as uintptr_t); + + Ok(()) +} + +pub fn get_chat_media( + context: &Context, + chat_id: u32, + msg_type: Viewtype, + msg_type2: Viewtype, + msg_type3: Viewtype, +) -> *mut dc_array_t { + context.sql.query_map( + "SELECT id FROM msgs WHERE chat_id=? AND (type=? OR type=? OR type=?) ORDER BY timestamp, id;", + params![ + chat_id as i32, + msg_type, + if msg_type2 != Viewtype::Unknown { + msg_type2 + } else { + msg_type + }, if msg_type3 != Viewtype::Unknown { + msg_type3 + } else { + msg_type + }, + ], + |row| row.get::<_, i32>(0), + |ids| { + let mut ret = Vec::new(); + for id in ids { + ret.push(id? as u32); + } + Ok(dc_array_t::from(ret).into_raw()) + } + ).unwrap_or_else(|_| std::ptr::null_mut()) +} + +pub unsafe fn get_next_media( + context: &Context, + curr_msg_id: u32, + dir: libc::c_int, + msg_type: Viewtype, + msg_type2: Viewtype, + msg_type3: Viewtype, +) -> u32 { + let mut ret_msg_id: u32 = 0i32 as u32; + let msg: *mut dc_msg_t = dc_msg_new_untyped(context); + let mut list: *mut dc_array_t = ptr::null_mut(); + let mut i: libc::c_int; + let cnt: libc::c_int; + + if dc_msg_load_from_db(msg, context, curr_msg_id) { + list = get_chat_media( + context, + (*msg).chat_id, + if msg_type != Viewtype::Unknown { + msg_type + } else { + (*msg).type_0 + }, + msg_type2, + msg_type3, + ); + if !list.is_null() { + cnt = dc_array_get_cnt(list) as libc::c_int; + i = 0i32; + while i < cnt { + if curr_msg_id == dc_array_get_id(list, i as size_t) { + if dir > 0i32 { + if i + 1i32 < cnt { + ret_msg_id = dc_array_get_id(list, (i + 1i32) as size_t) + } + } else if dir < 0i32 { + if i - 1i32 >= 0i32 { + ret_msg_id = dc_array_get_id(list, (i - 1i32) as size_t) + } + } + break; + } else { + i += 1 + } + } + } + } + + if !list.is_null() { + dc_array_unref(list); + } + dc_msg_unref(msg); + ret_msg_id +} + +pub fn archive(context: &Context, chat_id: u32, archive: bool) -> Result<(), Error> { + ensure!( + chat_id > DC_CHAT_ID_LAST_SPECIAL as u32, + "bad chat_id = {} <= 9", + chat_id + ); + + if archive { + sql::execute( + context, + &context.sql, + "UPDATE msgs SET state=? WHERE chat_id=? AND state=?;", + params![DC_STATE_IN_NOTICED, chat_id as i32, DC_STATE_IN_FRESH], + )?; + } + + sql::execute( + context, + &context.sql, + "UPDATE chats SET archived=? WHERE id=?;", + params![archive, chat_id as i32], + )?; + + context.call_cb(Event::MSGS_CHANGED, 0 as uintptr_t, 0 as uintptr_t); + + Ok(()) +} + +pub fn delete(context: &Context, chat_id: u32) -> Result<(), Error> { + ensure!( + chat_id > DC_CHAT_ID_LAST_SPECIAL as u32, + "bad chat_id = {} <= 9", + chat_id + ); + /* Up to 2017-11-02 deleting a group also implied leaving it, see above why we have changed this. */ + + let _chat = Chat::load_from_db(context, chat_id)?; + sql::execute( + context, + &context.sql, + "DELETE FROM msgs_mdns WHERE msg_id IN (SELECT id FROM msgs WHERE chat_id=?);", + params![chat_id as i32], + )?; + + sql::execute( + context, + &context.sql, + "DELETE FROM msgs WHERE chat_id=?;", + params![chat_id as i32], + )?; + + sql::execute( + context, + &context.sql, + "DELETE FROM chats_contacts WHERE chat_id=?;", + params![chat_id as i32], + )?; + + sql::execute( + context, + &context.sql, + "DELETE FROM chats WHERE id=?;", + params![chat_id as i32], + )?; + + context.call_cb(Event::MSGS_CHANGED, 0 as uintptr_t, 0 as uintptr_t); + + dc_job_kill_action(context, 105); + unsafe { dc_job_add(context, 105, 0, Params::new(), 10) }; + + Ok(()) +} + +pub fn get_chat_contacts(context: &Context, chat_id: u32) -> Vec { + /* Normal chats do not include SELF. Group chats do (as it may happen that one is deleted from a + groupchat but the chats stays visible, moreover, this makes displaying lists easier) */ + + if chat_id == 1 { + return Vec::new(); + } + + // we could also create a list for all contacts in the deaddrop by searching contacts belonging to chats with + // chats.blocked=2, however, currently this is not needed + + context + .sql + .query_map( + "SELECT cc.contact_id FROM chats_contacts cc \ + LEFT JOIN contacts c ON c.id=cc.contact_id WHERE cc.chat_id=? \ + ORDER BY c.id=1, LOWER(c.name||c.addr), c.id;", + params![chat_id], + |row| row.get::<_, u32>(0), + |ids| ids.collect::, _>>().map_err(Into::into), + ) + .unwrap_or_default() +} + +pub unsafe fn create_group_chat( + context: &Context, + verified: VerifiedStatus, + chat_name: impl AsRef, +) -> Result { + ensure!(!chat_name.as_ref().is_empty(), "Invalid chat name"); + + let draft_txt = + CString::new(context.stock_string_repl_str(StockMessage::NewGroupDraft, &chat_name)) + .unwrap(); + let grpid = dc_create_id(); + + sql::execute( + context, + &context.sql, + "INSERT INTO chats (type, name, grpid, param) VALUES(?, ?, ?, \'U=1\');", + params![ + if verified != VerifiedStatus::Unverified { + Chattype::VerifiedGroup + } else { + Chattype::Group + }, + chat_name.as_ref(), + grpid + ], + )?; + + let chat_id = sql::get_rowid(context, &context.sql, "chats", "grpid", grpid); + + if chat_id != 0 { + if 0 != add_to_chat_contacts_table(context, chat_id, 1) { + let draft_msg = dc_msg_new(context, Viewtype::Text); + dc_msg_set_text(draft_msg, draft_txt.as_ptr()); + set_draft_raw(context, chat_id, draft_msg); + dc_msg_unref(draft_msg); + } + + context.call_cb(Event::MSGS_CHANGED, 0 as uintptr_t, 0 as uintptr_t); + } + + Ok(chat_id) +} + +/* you MUST NOT modify this or the following strings */ +// Context functions to work with chats +// TODO should return bool /rtn +pub fn add_to_chat_contacts_table(context: &Context, chat_id: u32, contact_id: u32) -> libc::c_int { + // add a contact to a chat; the function does not check the type or if any of the record exist or are already + // added to the chat! + sql::execute( + context, + &context.sql, + "INSERT INTO chats_contacts (chat_id, contact_id) VALUES(?, ?)", + params![chat_id as i32, contact_id as i32], + ) + .is_ok() as libc::c_int +} + +pub unsafe fn add_contact_to_chat(context: &Context, chat_id: u32, contact_id: u32) -> libc::c_int { + add_contact_to_chat_ex(context, chat_id, contact_id, 0) +} + +// TODO should return bool /rtn +#[allow(non_snake_case)] +pub unsafe fn add_contact_to_chat_ex( + context: &Context, + chat_id: u32, + contact_id: u32, + flags: libc::c_int, +) -> libc::c_int { + let mut OK_TO_CONTINUE = true; + let mut success: libc::c_int = 0; + let contact = Contact::get_by_id(context, contact_id); + + if contact.is_err() || chat_id <= DC_CHAT_ID_LAST_SPECIAL as u32 { + return 0; + } + let mut msg = dc_msg_new_untyped(context); + + reset_gossiped_timestamp(context, chat_id); + let contact = contact.unwrap(); + + /*this also makes sure, not contacts are added to special or normal chats*/ + let chat = Chat::load_from_db(context, chat_id); + + if !(!real_group_exists(context, chat_id) + || !Contact::real_exists_by_id(context, contact_id) + && contact_id != DC_CONTACT_ID_SELF as u32 + || chat.is_err()) + { + let mut chat = chat.unwrap(); + + if !(is_contact_in_chat(context, chat_id, 1 as u32) == 1) { + log_event!( + context, + Event::ERROR_SELF_NOT_IN_GROUP, + 0, + "Cannot add contact to group; self not in group.", + ); + } else { + /* we should respect this - whatever we send to the group, it gets discarded anyway! */ + if 0 != flags & 0x1 && chat.param.get_int(Param::Unpromoted).unwrap_or_default() == 1 { + chat.param.remove(Param::Unpromoted); + chat.update_param().unwrap(); + } + let self_addr = context + .sql + .get_config(context, "configured_addr") + .unwrap_or_default(); + if contact.get_addr() != &self_addr { + // ourself is added using DC_CONTACT_ID_SELF, do not add it explicitly. + // if SELF is not in the group, members cannot be added at all. + + if 0 != is_contact_in_chat(context, chat_id, contact_id) { + if 0 == flags & 0x1 { + success = 1; + OK_TO_CONTINUE = false; + } + } else { + // else continue and send status mail + if chat.typ == Chattype::VerifiedGroup { + if contact.is_verified() != VerifiedStatus::BidirectVerified { + error!( + context, 0, + "Only bidirectional verified contacts can be added to verified groups." + ); + OK_TO_CONTINUE = false; + } + } + if OK_TO_CONTINUE { + if 0 == add_to_chat_contacts_table(context, chat_id, contact_id) { + OK_TO_CONTINUE = false; + } + } + } + if OK_TO_CONTINUE { + if chat.param.get_int(Param::Unpromoted).unwrap_or_default() == 0 { + (*msg).type_0 = Viewtype::Text; + (*msg).text = Some(context.stock_system_msg( + StockMessage::MsgAddMember, + contact.get_addr(), + "", + DC_CONTACT_ID_SELF as u32, + )); + (*msg).param.set_int(Param::Cmd, 4); + (*msg).param.set(Param::Arg, contact.get_addr()); + (*msg).param.set_int(Param::Arg2, flags); + (*msg).id = send_msg(context, chat_id, msg).unwrap_or_default(); + context.call_cb( + Event::MSGS_CHANGED, + chat_id as uintptr_t, + (*msg).id as uintptr_t, + ); + } + context.call_cb(Event::MSGS_CHANGED, chat_id as uintptr_t, 0 as uintptr_t); + success = 1; + } + } + } + } + + dc_msg_unref(msg); + + success +} + +fn real_group_exists(context: &Context, chat_id: u32) -> bool { + // check if a group or a verified group exists under the given ID + if !context.sql.is_open() || chat_id <= DC_CHAT_ID_LAST_SPECIAL as u32 { + return false; + } + + context + .sql + .exists( + "SELECT id FROM chats WHERE id=? AND (type=120 OR type=130);", + params![chat_id as i32], + ) + .unwrap_or_default() +} + +pub fn reset_gossiped_timestamp(context: &Context, chat_id: u32) { + set_gossiped_timestamp(context, chat_id, 0); +} + +// Should return Result +pub fn set_gossiped_timestamp(context: &Context, chat_id: u32, timestamp: i64) { + if 0 != chat_id { + info!( + context, + 0, "set gossiped_timestamp for chat #{} to {}.", chat_id, timestamp, + ); + + sql::execute( + context, + &context.sql, + "UPDATE chats SET gossiped_timestamp=? WHERE id=?;", + params![timestamp, chat_id as i32], + ) + .ok(); + } else { + info!( + context, + 0, "set gossiped_timestamp for all chats to {}.", timestamp, + ); + sql::execute( + context, + &context.sql, + "UPDATE chats SET gossiped_timestamp=?;", + params![timestamp], + ) + .ok(); + } +} + +pub unsafe fn remove_contact_from_chat( + context: &Context, + chat_id: u32, + contact_id: u32, +) -> Result<(), Error> { + ensure!( + chat_id > DC_CHAT_ID_LAST_SPECIAL as u32, + "bad chat_id = {} <= 9", + chat_id + ); + ensure!( + contact_id != DC_CONTACT_ID_SELF as u32, + "Cannot remove self" + ); + + let mut msg = dc_msg_new_untyped(context); + let mut success = false; + + /* we do not check if "contact_id" exists but just delete all records with the id from chats_contacts */ + /* this allows to delete pending references to deleted contacts. Of course, this should _not_ happen. */ + let chat = Chat::load_from_db(context, chat_id); + + if !(!real_group_exists(context, chat_id) || chat.is_err()) { + let chat = chat.unwrap(); + if !(is_contact_in_chat(context, chat_id, 1 as u32) == 1) { + log_event!( + context, + Event::ERROR_SELF_NOT_IN_GROUP, + 0, + "Cannot remove contact from chat; self not in group.", + ); + } else { + /* we should respect this - whatever we send to the group, it gets discarded anyway! */ + if let Ok(contact) = Contact::get_by_id(context, contact_id) { + if chat.param.get_int(Param::Unpromoted).unwrap_or_default() == 0 { + (*msg).type_0 = Viewtype::Text; + if contact.id == DC_CONTACT_ID_SELF as u32 { + set_group_explicitly_left(context, chat.grpid).unwrap(); + (*msg).text = Some(context.stock_system_msg( + StockMessage::MsgGroupLeft, + "", + "", + DC_CONTACT_ID_SELF as u32, + )); + } else { + (*msg).text = Some(context.stock_system_msg( + StockMessage::MsgDelMember, + contact.get_addr(), + "", + DC_CONTACT_ID_SELF as u32, + )); + } + (*msg).param.set_int(Param::Cmd, 5); + (*msg).param.set(Param::Arg, contact.get_addr()); + (*msg).id = send_msg(context, chat_id, msg).unwrap_or_default(); + context.call_cb( + Event::MSGS_CHANGED, + chat_id as uintptr_t, + (*msg).id as uintptr_t, + ); + } + } + if sql::execute( + context, + &context.sql, + "DELETE FROM chats_contacts WHERE chat_id=? AND contact_id=?;", + params![chat_id as i32, contact_id as i32], + ) + .is_ok() + { + context.call_cb(Event::CHAT_MODIFIED, chat_id as uintptr_t, 0 as uintptr_t); + success = true; + } + } + } + + dc_msg_unref(msg); + + if !success { + bail!("Failed to remove contact"); + } + + Ok(()) +} + +fn set_group_explicitly_left(context: &Context, grpid: impl AsRef) -> Result<(), Error> { + if !is_group_explicitly_left(context, grpid.as_ref())? { + sql::execute( + context, + &context.sql, + "INSERT INTO leftgrps (grpid) VALUES(?);", + params![grpid.as_ref()], + )?; + } + + Ok(()) +} + +pub fn is_group_explicitly_left(context: &Context, grpid: impl AsRef) -> Result { + context.sql.exists( + "SELECT id FROM leftgrps WHERE grpid=?;", + params![grpid.as_ref()], + ) +} + +pub unsafe fn set_chat_name( + context: &Context, + chat_id: u32, + new_name: impl AsRef, +) -> Result<(), Error> { + /* the function only sets the names of group chats; normal chats get their names from the contacts */ + let mut success = false; + + ensure!(!new_name.as_ref().is_empty(), "Invalid name"); + ensure!(chat_id > DC_CHAT_ID_LAST_SPECIAL as u32, "Invalid chat ID"); + + let chat = Chat::load_from_db(context, chat_id)?; + let mut msg = dc_msg_new_untyped(context); + + if real_group_exists(context, chat_id) { + if &chat.name == new_name.as_ref() { + success = true; + } else if !(is_contact_in_chat(context, chat_id, 1) == 1) { + log_event!( + context, + Event::ERROR_SELF_NOT_IN_GROUP, + 0, + "Cannot set chat name; self not in group", + ); + } else { + /* we should respect this - whatever we send to the group, it gets discarded anyway! */ + if sql::execute( + context, + &context.sql, + format!( + "UPDATE chats SET name='{}' WHERE id={};", + new_name.as_ref(), + chat_id as i32 + ), + params![], + ) + .is_ok() + { + if chat.param.get_int(Param::Unpromoted).unwrap_or_default() == 0 { + (*msg).type_0 = Viewtype::Text; + (*msg).text = Some(context.stock_system_msg( + StockMessage::MsgGrpName, + &chat.name, + new_name.as_ref(), + DC_CONTACT_ID_SELF as u32, + )); + (*msg).param.set_int(Param::Cmd, 2); + if !chat.name.is_empty() { + (*msg).param.set(Param::Arg, &chat.name); + } + (*msg).id = send_msg(context, chat_id, msg).unwrap_or_default(); + context.call_cb( + Event::MSGS_CHANGED, + chat_id as uintptr_t, + (*msg).id as uintptr_t, + ); + } + context.call_cb( + Event::CHAT_MODIFIED, + chat_id as uintptr_t, + 0i32 as uintptr_t, + ); + success = true; + } + } + } + + dc_msg_unref(msg); + + if !success { + bail!("Failed to set name"); + } + + Ok(()) +} + +#[allow(non_snake_case)] +pub unsafe fn set_chat_profile_image( + context: &Context, + chat_id: u32, + new_image: impl AsRef, +) -> Result<(), Error> { + ensure!(chat_id > DC_CHAT_ID_LAST_SPECIAL as u32, "Invalid chat ID"); + + let mut OK_TO_CONTINUE = true; + let mut success = false; + + let mut chat = Chat::load_from_db(context, chat_id)?; + let mut msg = dc_msg_new_untyped(context); + let mut new_image_rel = None; + + if real_group_exists(context, chat_id) { + if !(is_contact_in_chat(context, chat_id, 1i32 as u32) == 1i32) { + log_event!( + context, + Event::ERROR_SELF_NOT_IN_GROUP, + 0, + "Cannot set chat profile image; self not in group.", + ); + } else { + /* we should respect this - whatever we send to the group, it gets discarded anyway! */ + if !new_image.as_ref().is_empty() { + let mut img = new_image.as_ref().to_string(); + if !dc_make_rel_and_copy(context, &mut img) { + OK_TO_CONTINUE = false; + } + new_image_rel = Some(img); + } else { + OK_TO_CONTINUE = false; + } + } + if OK_TO_CONTINUE { + if let Some(ref new_image_rel) = new_image_rel { + chat.param.set(Param::ProfileImage, new_image_rel); + } + if chat.update_param().is_ok() { + if chat.param.get_int(Param::Unpromoted).unwrap_or_default() == 0 { + (*msg).param.set_int(Param::Cmd, 3); + if let Some(ref new_image_rel) = new_image_rel { + (*msg).param.set(Param::Arg, new_image_rel); + } + (*msg).type_0 = Viewtype::Text; + (*msg).text = Some(context.stock_system_msg( + if new_image_rel.is_some() { + StockMessage::MsgGrpImgChanged + } else { + StockMessage::MsgGrpImgDeleted + }, + "", + "", + DC_CONTACT_ID_SELF as u32, + )); + (*msg).id = send_msg(context, chat_id, msg).unwrap_or_default(); + context.call_cb( + Event::MSGS_CHANGED, + chat_id as uintptr_t, + (*msg).id as uintptr_t, + ); + } + context.call_cb( + Event::CHAT_MODIFIED, + chat_id as uintptr_t, + 0i32 as uintptr_t, + ); + success = true; + } + } + } + + dc_msg_unref(msg); + + if !success { + bail!("Failed to set profile image"); + } + + Ok(()) +} + +pub unsafe fn forward_msgs( + context: &Context, + msg_ids: *const u32, + msg_cnt: libc::c_int, + chat_id: u32, +) { + if msg_ids.is_null() || msg_cnt <= 0 || chat_id <= DC_CHAT_ID_LAST_SPECIAL as u32 { + return; + } + + let msg = dc_msg_new_untyped(context); + let mut created_db_entries = Vec::new(); + let mut curr_timestamp: i64; + + unarchive(context, chat_id).unwrap(); + if let Ok(mut chat) = Chat::load_from_db(context, chat_id) { + curr_timestamp = dc_create_smeared_timestamps(context, msg_cnt); + let idsstr = std::slice::from_raw_parts(msg_ids, msg_cnt as usize) + .iter() + .enumerate() + .fold( + String::with_capacity(2 * msg_cnt as usize), + |acc, (i, n)| (if i == 0 { acc } else { acc + "," }) + &n.to_string(), + ); + + let ids = context + .sql + .query_map( + format!( + "SELECT id FROM msgs WHERE id IN({}) ORDER BY timestamp,id", + idsstr + ), + params![], + |row| row.get::<_, i32>(0), + |ids| ids.collect::, _>>().map_err(Into::into), + ) + .unwrap(); // TODO: better error handling + + for id in ids { + let src_msg_id = id; + if !dc_msg_load_from_db(msg, context, src_msg_id as u32) { + break; + } + let original_param = (*msg).param.clone(); + if (*msg).from_id != DC_CONTACT_ID_SELF as u32 { + (*msg).param.set_int(Param::Forwarded, 1); + } + (*msg).param.remove(Param::GuranteeE2ee); + (*msg).param.remove(Param::ForcePlaintext); + (*msg).param.remove(Param::Cmd); + + let new_msg_id: u32; + if (*msg).state == DC_STATE_OUT_PREPARING { + let fresh9 = curr_timestamp; + curr_timestamp = curr_timestamp + 1; + new_msg_id = chat + .prepare_msg_raw(context, msg, fresh9) + .unwrap_or_default(); + let save_param = (*msg).param.clone(); + (*msg).param = original_param; + (*msg).id = src_msg_id as u32; + + if let Some(old_fwd) = (*msg).param.get(Param::PrepForwards) { + let new_fwd = format!("{} {}", old_fwd, new_msg_id); + (*msg).param.set(Param::PrepForwards, new_fwd); + } else { + (*msg) + .param + .set(Param::PrepForwards, new_msg_id.to_string()); + } + + dc_msg_save_param_to_disk(msg); + (*msg).param = save_param; + } else { + (*msg).state = DC_STATE_OUT_PENDING; + let fresh10 = curr_timestamp; + curr_timestamp = curr_timestamp + 1; + new_msg_id = chat + .prepare_msg_raw(context, msg, fresh10) + .unwrap_or_default(); + dc_job_send_msg(context, new_msg_id); + } + created_db_entries.push(chat_id); + created_db_entries.push(new_msg_id); + } + } + + for i in (0..created_db_entries.len()).step_by(2) { + context.call_cb( + Event::MSGS_CHANGED, + created_db_entries[i] as uintptr_t, + created_db_entries[i + 1] as uintptr_t, + ); + } + dc_msg_unref(msg); +} + +pub fn get_chat_contact_cnt(context: &Context, chat_id: u32) -> libc::c_int { + context + .sql + .query_row_col( + context, + "SELECT COUNT(*) FROM chats_contacts WHERE chat_id=?;", + params![chat_id as i32], + 0, + ) + .unwrap_or_default() +} + +pub fn get_chat_cnt(context: &Context) -> usize { + if context.sql.is_open() { + /* no database, no chats - this is no error (needed eg. for information) */ + context + .sql + .query_row_col::<_, isize>( + context, + "SELECT COUNT(*) FROM chats WHERE id>9 AND blocked=0;", + params![], + 0, + ) + .unwrap_or_default() as usize + } else { + 0 + } +} + +pub unsafe fn get_chat_id_by_grpid( + context: &Context, + grpid: impl AsRef, + ret_blocked: Option<&mut Blocked>, + ret_verified: *mut libc::c_int, +) -> u32 { + if !ret_verified.is_null() { + *ret_verified = 0; + } + + context + .sql + .query_row( + "SELECT id, blocked, type FROM chats WHERE grpid=?;", + params![grpid.as_ref()], + |row| { + let chat_id = row.get(0)?; + + if let Some(b) = ret_blocked { + *b = row.get::<_, Option>(1)?.unwrap_or_default(); + } + + let v = row.get::<_, Option>(2)?.unwrap_or_default(); + *ret_verified = (v == Chattype::VerifiedGroup) as libc::c_int; + + Ok(chat_id) + }, + ) + .unwrap_or_default() +} + +pub fn add_device_msg(context: &Context, chat_id: u32, text: impl AsRef) { + let rfc724_mid = unsafe { + dc_create_outgoing_rfc724_mid( + ptr::null(), + b"@device\x00" as *const u8 as *const libc::c_char, + ) + }; + + if context.sql.execute( + "INSERT INTO msgs (chat_id,from_id,to_id, timestamp,type,state, txt,rfc724_mid) VALUES (?,?,?, ?,?,?, ?,?);", + params![ + chat_id as i32, + 2, + 2, + dc_create_smeared_timestamp(context), + Viewtype::Text, + DC_STATE_IN_NOTICED, + text.as_ref(), + as_str(rfc724_mid), + ] + ).is_err() { + unsafe { free(rfc724_mid as *mut libc::c_void) }; + return; + } + + let msg_id = sql::get_rowid( + context, + &context.sql, + "msgs", + "rfc724_mid", + as_str(rfc724_mid), + ); + unsafe { free(rfc724_mid as *mut libc::c_void) }; + context.call_cb( + Event::MSGS_CHANGED, + chat_id as uintptr_t, + msg_id as uintptr_t, + ); +} diff --git a/src/chatlist.rs b/src/chatlist.rs index c675f3b64..4fc1c308c 100644 --- a/src/chatlist.rs +++ b/src/chatlist.rs @@ -1,7 +1,7 @@ +use crate::chat::*; use crate::constants::*; use crate::contact::*; use crate::context::*; -use crate::dc_chat::*; use crate::dc_lot::*; use crate::dc_msg::*; use crate::dc_tools::*; @@ -249,7 +249,7 @@ impl<'a> Chatlist<'a> { /// - 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 unsafe fn get_summary(&self, index: usize, mut chat: *mut Chat<'a>) -> *mut dc_lot_t { + pub unsafe fn get_summary(&self, index: usize, chat: Option<&Chat<'a>>) -> *mut dc_lot_t { // 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". @@ -261,27 +261,27 @@ impl<'a> Chatlist<'a> { return ret; } - let lastmsg_id = self.ids[index].1; - let mut lastcontact = None; - - if chat.is_null() { - chat = dc_chat_new(self.context); - let chat_to_delete = chat; - if !dc_chat_load_from_db(chat, self.ids[index].0) { - (*ret).text2 = "ErrCannotReadChat".strdup(); - dc_chat_unref(chat_to_delete); - + let chat_loaded: Chat; + let chat = if let Some(chat) = chat { + chat + } else { + if let Ok(chat) = Chat::load_from_db(self.context, self.ids[index].0) { + chat_loaded = chat; + &chat_loaded + } else { return ret; } - } + }; + + let lastmsg_id = self.ids[index].1; + let mut lastcontact = None; let lastmsg = if 0 != lastmsg_id { let lastmsg = dc_msg_new_untyped(self.context); dc_msg_load_from_db(lastmsg, self.context, lastmsg_id); if (*lastmsg).from_id != 1 as libc::c_uint - && ((*chat).type_0 == DC_CHAT_TYPE_GROUP - || (*chat).type_0 == DC_CHAT_TYPE_VERIFIED_GROUP) + && (chat.typ == Chattype::Group || chat.typ == Chattype::VerifiedGroup) { lastcontact = Contact::load_from_db(self.context, (*lastmsg).from_id).ok(); } @@ -290,7 +290,7 @@ impl<'a> Chatlist<'a> { std::ptr::null_mut() }; - if (*chat).id == DC_CHAT_ID_ARCHIVED_LINK as u32 { + if chat.id == DC_CHAT_ID_ARCHIVED_LINK as u32 { (*ret).text2 = dc_strdup(ptr::null()) } else if lastmsg.is_null() || (*lastmsg).from_id == DC_CONTACT_ID_UNDEFINED as u32 { (*ret).text2 = self.context.stock_str(StockMessage::NoMessages).strdup(); diff --git a/src/constants.rs b/src/constants.rs index b0aca01b7..102636a8c 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -26,9 +26,19 @@ const DC_SENTBOX_WATCH_DEFAULT: i32 = 1; const DC_MVBOX_WATCH_DEFAULT: i32 = 1; const DC_MVBOX_MOVE_DEFAULT: i32 = 1; -pub const DC_CHAT_NOT_BLOCKED: i32 = 0; -const DC_CHAT_MANUALLY_BLOCKED: i32 = 1; -pub const DC_CHAT_DEADDROP_BLOCKED: i32 = 2; +#[derive(Debug, Display, Clone, Copy, PartialEq, Eq, FromPrimitive, ToPrimitive, FromSql, ToSql)] +#[repr(u8)] +pub enum Blocked { + Not = 0, + Manually = 1, + Deaddrop = 2, +} + +impl Default for Blocked { + fn default() -> Self { + Blocked::Not + } +} pub const DC_IMAP_SEEN: u32 = 0x1; @@ -88,10 +98,32 @@ pub const DC_CHAT_ID_ALLDONE_HINT: usize = 7; /// larger chat IDs are "real" chats, their messages are "real" messages. pub const DC_CHAT_ID_LAST_SPECIAL: usize = 9; -const DC_CHAT_TYPE_UNDEFINED: i32 = 0; -pub const DC_CHAT_TYPE_SINGLE: i32 = 100; -pub const DC_CHAT_TYPE_GROUP: i32 = 120; -pub const DC_CHAT_TYPE_VERIFIED_GROUP: i32 = 130; +#[derive( + Debug, + Display, + Clone, + Copy, + PartialEq, + Eq, + FromPrimitive, + ToPrimitive, + FromSql, + ToSql, + IntoStaticStr, +)] +#[repr(u32)] +pub enum Chattype { + Undefined = 0, + Single = 100, + Group = 120, + VerifiedGroup = 130, +} + +impl Default for Chattype { + fn default() -> Self { + Chattype::Undefined + } +} pub const DC_MSG_ID_MARKER1: usize = 1; const DC_MSG_ID_DAYMARKER: usize = 9; diff --git a/src/contact.rs b/src/contact.rs index 62024bcd2..799256d3f 100644 --- a/src/contact.rs +++ b/src/contact.rs @@ -138,7 +138,7 @@ pub enum Modifier { Created, } -#[derive(Debug, PartialEq, Eq, Clone, Copy)] +#[derive(Debug, PartialEq, Eq, Clone, Copy, FromPrimitive)] #[repr(u8)] pub enum VerifiedStatus { /// Contact is not verified. @@ -791,7 +791,7 @@ impl<'a> Contact<'a> { /// and can be used for an fallback avatar with white initials /// as well as for headlines in bubbles of group chats. pub fn get_color(&self) -> u32 { - dc_str_to_color_safe(&self.addr) + dc_str_to_color(&self.addr) } /// Check if a contact was verified. E.g. by a secure-join QR code scan diff --git a/src/context.rs b/src/context.rs index 9ca80c361..269046201 100644 --- a/src/context.rs +++ b/src/context.rs @@ -1,9 +1,9 @@ use std::sync::{Arc, Condvar, Mutex, RwLock}; +use crate::chat::*; 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::*; @@ -338,7 +338,7 @@ pub unsafe fn dc_get_info(context: &Context) -> *mut libc::c_char { 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 chats = 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; diff --git a/src/dc_chat.rs b/src/dc_chat.rs deleted file mode 100644 index 5843b16d3..000000000 --- a/src/dc_chat.rs +++ /dev/null @@ -1,2269 +0,0 @@ -use std::ffi::CString; - -use crate::chatlist::*; -use crate::constants::*; -use crate::contact::*; -use crate::context::Context; -use crate::dc_array::*; -use crate::dc_job::*; -use crate::dc_msg::*; -use crate::dc_tools::*; -use crate::param::*; -use crate::sql::{self, Sql}; -use crate::stock::StockMessage; -use crate::types::*; -use crate::x::*; -use std::ptr; - -/** - * @class dc_chat_t - * - * An object representing a single chat in memory. - * Chat objects are created using eg. dc_get_chat() - * and are not updated on database changes; - * if you want an update, you have to recreate the object. - */ -#[derive(Clone)] -pub struct Chat<'a> { - pub id: uint32_t, - pub type_0: libc::c_int, - pub name: *mut libc::c_char, - archived: libc::c_int, - pub context: &'a Context, - pub grpid: *mut libc::c_char, - blocked: libc::c_int, - pub param: Params, - pub gossiped_timestamp: i64, - is_sending_locations: libc::c_int, -} - -// handle chats -pub unsafe fn dc_create_chat_by_msg_id(context: &Context, msg_id: uint32_t) -> uint32_t { - let mut chat_id: uint32_t = 0i32 as uint32_t; - let mut send_event: libc::c_int = 0i32; - let msg: *mut dc_msg_t = dc_msg_new_untyped(context); - let chat: *mut Chat = dc_chat_new(context); - if dc_msg_load_from_db(msg, context, msg_id) - && dc_chat_load_from_db(chat, (*msg).chat_id) - && (*chat).id > 9i32 as libc::c_uint - { - chat_id = (*chat).id; - if 0 != (*chat).blocked { - dc_unblock_chat(context, (*chat).id); - send_event = 1i32 - } - Contact::scaleup_origin_by_id(context, (*msg).from_id, Origin::CreateChat); - } - - dc_msg_unref(msg); - dc_chat_unref(chat); - if 0 != send_event { - context.call_cb(Event::MSGS_CHANGED, 0i32 as uintptr_t, 0i32 as uintptr_t); - } - chat_id -} - -pub unsafe fn dc_chat_new<'a>(context: &'a Context) -> *mut Chat<'a> { - let chat = Chat { - id: 0, - type_0: 0, - name: std::ptr::null_mut(), - archived: 0, - context, - grpid: std::ptr::null_mut(), - blocked: 0, - param: Params::new(), - gossiped_timestamp: 0, - is_sending_locations: 0, - }; - - Box::into_raw(Box::new(chat)) -} - -pub unsafe fn dc_chat_unref(chat: *mut Chat) { - if chat.is_null() { - return; - } - dc_chat_empty(chat); - Box::from_raw(chat); -} - -unsafe fn dc_chat_empty(mut chat: *mut Chat) { - if chat.is_null() { - return; - } - free((*chat).name as *mut libc::c_void); - (*chat).name = ptr::null_mut(); - (*chat).type_0 = 0i32; - (*chat).id = 0i32 as uint32_t; - free((*chat).grpid as *mut libc::c_void); - (*chat).grpid = ptr::null_mut(); - (*chat).blocked = 0i32; - (*chat).gossiped_timestamp = 0; - (*chat).param = Params::new(); -} - -pub unsafe fn dc_unblock_chat(context: &Context, chat_id: uint32_t) { - dc_block_chat(context, chat_id, 0i32); -} - -fn dc_block_chat(context: &Context, chat_id: u32, new_blocking: libc::c_int) -> bool { - sql::execute( - context, - &context.sql, - "UPDATE chats SET blocked=? WHERE id=?;", - params![new_blocking, chat_id as i32], - ) - .is_ok() -} - -pub fn dc_chat_load_from_db(chat: *mut Chat, chat_id: u32) -> bool { - if chat.is_null() { - return false; - } - unsafe { dc_chat_empty(chat) }; - - let context = unsafe { (*chat).context }; - - let res = context.sql.query_row( - "SELECT c.id,c.type,c.name, c.grpid,c.param,c.archived, \ - c.blocked, c.gossiped_timestamp, c.locations_send_until \ - FROM chats c WHERE c.id=?;", - params![chat_id as i32], - |row| { - let c = unsafe { &mut *chat }; - - c.id = row.get(0)?; - c.type_0 = row.get(1)?; - c.name = unsafe { row.get::<_, String>(2)?.strdup() }; - c.grpid = unsafe { row.get::<_, String>(3)?.strdup() }; - - c.param = row.get::<_, String>(4)?.parse().unwrap_or_default(); - c.archived = row.get(5)?; - c.blocked = row.get::<_, Option>(6)?.unwrap_or_default(); - c.gossiped_timestamp = row.get(7)?; - c.is_sending_locations = row.get(8)?; - - Ok(()) - }, - ); - - match res { - Err(crate::error::Error::Sql(rusqlite::Error::QueryReturnedNoRows)) => false, - Err(err) => match err { - _ => { - error!( - context, - 0, "chat: failed to load from db {}: {:?}", chat_id, err - ); - false - } - }, - Ok(_) => { - let c = unsafe { &mut *chat }; - match c.id { - 1 => unsafe { - free((*chat).name as *mut libc::c_void); - (*chat).name = (*chat).context.stock_str(StockMessage::DeadDrop).strdup(); - }, - 6 => unsafe { - free((*chat).name as *mut libc::c_void); - let tempname = (*chat).context.stock_str(StockMessage::ArchivedChats); - let cnt = dc_get_archived_cnt((*chat).context); - (*chat).name = format!("{} ({})", tempname, cnt).strdup(); - }, - 5 => unsafe { - free((*chat).name as *mut libc::c_void); - (*chat).name = (*chat) - .context - .stock_str(StockMessage::StarredMsgs) - .strdup(); - }, - _ => { - unsafe { - if (*chat).type_0 == DC_CHAT_TYPE_SINGLE { - free((*chat).name as *mut libc::c_void); - let contacts = dc_get_chat_contacts((*chat).context, (*chat).id); - let mut chat_name = "Err [Name not found]".to_owned(); - if !(*contacts).is_empty() { - if let Ok(contact) = - Contact::get_by_id((*chat).context, contacts[0]) - { - chat_name = contact.get_display_name().to_owned(); - } - } - (*chat).name = (&chat_name).strdup(); - } - } - - if unsafe { &(*chat).param }.exists(Param::Selftalk) { - unsafe { - free((*chat).name as *mut libc::c_void); - (*chat).name = - (*chat).context.stock_str(StockMessage::SelfMsg).strdup(); - } - } - } - } - true - } - } -} - -pub unsafe fn dc_create_chat_by_contact_id(context: &Context, contact_id: uint32_t) -> uint32_t { - let mut chat_id = 0; - let mut chat_blocked = 0; - let mut send_event = 0; - dc_lookup_real_nchat_by_contact_id(context, contact_id, &mut chat_id, &mut chat_blocked); - if 0 != chat_id { - if 0 != chat_blocked { - dc_unblock_chat(context, chat_id); - send_event = 1i32 - } - } else if !Contact::real_exists_by_id(context, contact_id) - && contact_id != DC_CONTACT_ID_SELF as u32 - { - warn!( - context, - 0, "Cannot create chat, contact {} does not exist.", contact_id as libc::c_int, - ); - } else { - dc_create_or_lookup_nchat_by_contact_id( - context, - contact_id, - 0i32, - &mut chat_id, - ptr::null_mut(), - ); - if 0 != chat_id { - send_event = 1; - } - Contact::scaleup_origin_by_id(context, contact_id, Origin::CreateChat); - } - if 0 != send_event { - context.call_cb(Event::MSGS_CHANGED, 0i32 as uintptr_t, 0i32 as uintptr_t); - } - chat_id -} - -pub unsafe fn dc_create_or_lookup_nchat_by_contact_id( - context: &Context, - contact_id: uint32_t, - create_blocked: libc::c_int, - ret_chat_id: *mut uint32_t, - ret_chat_blocked: *mut libc::c_int, -) { - let mut chat_id = 0; - let mut chat_blocked = 0; - - if !ret_chat_id.is_null() { - *ret_chat_id = 0; - } - if !ret_chat_blocked.is_null() { - *ret_chat_blocked = 0; - } - if !context.sql.is_open() { - return; - } - if contact_id == 0 as libc::c_uint { - return; - } - dc_lookup_real_nchat_by_contact_id(context, contact_id, &mut chat_id, &mut chat_blocked); - if chat_id != 0 { - if !ret_chat_id.is_null() { - *ret_chat_id = chat_id - } - if !ret_chat_blocked.is_null() { - *ret_chat_blocked = chat_blocked - } - return; - } - if let Ok(contact) = Contact::load_from_db(context, contact_id) { - let chat_name = contact.get_display_name(); - - if sql::execute( - context, - &context.sql, - format!( - "INSERT INTO chats (type, name, param, blocked, grpid) VALUES({}, '{}', '{}', {}, '{}')", - 100, - chat_name, - if contact_id == DC_CONTACT_ID_SELF as u32 { "K=1" } else { "" }, - create_blocked, - contact.get_addr(), - ), - params![], - ).is_ok() { - chat_id = sql::get_rowid( - context, - &context.sql, - "chats", - "grpid", - contact.get_addr(), - ); - - sql::execute( - context, - &context.sql, - format!("INSERT INTO chats_contacts (chat_id, contact_id) VALUES({}, {})", chat_id, contact_id), - params![], - ).ok(); - } - } - - if !ret_chat_id.is_null() { - *ret_chat_id = chat_id - } - if !ret_chat_blocked.is_null() { - *ret_chat_blocked = create_blocked - }; -} - -pub fn dc_lookup_real_nchat_by_contact_id( - context: &Context, - contact_id: uint32_t, - ret_chat_id: *mut uint32_t, - ret_chat_blocked: *mut libc::c_int, -) { - /* checks for "real" chats or self-chat */ - if !ret_chat_id.is_null() { - unsafe { *ret_chat_id = 0 }; - } - if !ret_chat_blocked.is_null() { - unsafe { *ret_chat_blocked = 0 }; - } - if !context.sql.is_open() { - return; - } - - if let Ok((id, blocked)) = context.sql.query_row( - "SELECT c.id, c.blocked FROM chats c INNER JOIN chats_contacts j ON c.id=j.chat_id WHERE c.type=100 AND c.id>9 AND j.contact_id=?;", - params![contact_id as i32], - |row| Ok((row.get(0)?, row.get::<_, Option>(1)?.unwrap_or_default())), - ) { - unsafe { *ret_chat_id = id }; - unsafe { *ret_chat_blocked = blocked }; - } -} - -pub unsafe fn dc_get_chat_id_by_contact_id(context: &Context, contact_id: uint32_t) -> uint32_t { - let mut chat_id: uint32_t = 0i32 as uint32_t; - let mut chat_id_blocked: libc::c_int = 0i32; - dc_lookup_real_nchat_by_contact_id(context, contact_id, &mut chat_id, &mut chat_id_blocked); - if 0 != chat_id_blocked { - 0i32 as libc::c_uint - } else { - chat_id - } -} - -pub unsafe fn dc_prepare_msg<'a>( - context: &'a Context, - chat_id: uint32_t, - mut msg: *mut dc_msg_t<'a>, -) -> uint32_t { - if msg.is_null() || chat_id <= 9i32 as libc::c_uint { - return 0i32 as uint32_t; - } - (*msg).state = DC_STATE_OUT_PREPARING; - let msg_id: uint32_t = prepare_msg_common(context, chat_id, msg); - context.call_cb( - Event::MSGS_CHANGED, - (*msg).chat_id as uintptr_t, - (*msg).id as uintptr_t, - ); - msg_id -} - -pub fn msgtype_has_file(msgtype: Viewtype) -> bool { - match msgtype { - Viewtype::Image => true, - Viewtype::Gif => true, - Viewtype::Audio => true, - Viewtype::Voice => true, - Viewtype::Video => true, - Viewtype::File => true, - _ => false, - } -} - -#[allow(non_snake_case)] -unsafe fn prepare_msg_common<'a>( - context: &'a Context, - chat_id: uint32_t, - mut msg: *mut dc_msg_t<'a>, -) -> uint32_t { - let mut OK_TO_CONTINUE = true; - (*msg).id = 0i32 as uint32_t; - (*msg).context = context; - if (*msg).type_0 == Viewtype::Text { - /* the caller should check if the message text is empty */ - } else if msgtype_has_file((*msg).type_0) { - let mut pathNfilename = (*msg) - .param - .get(Param::File) - .map(|s| s.strdup()) - .unwrap_or_else(|| std::ptr::null_mut()); - if pathNfilename.is_null() { - error!( - context, - 0, - "Attachment missing for message of type #{}.", - (*msg).type_0, - ); - OK_TO_CONTINUE = false; - } else if (*msg).state == DC_STATE_OUT_PREPARING - && !dc_is_blobdir_path(context, pathNfilename) - { - error!(context, 0, "Files must be created in the blob-directory.",); - OK_TO_CONTINUE = false; - } else if !dc_make_rel_and_copy(context, &mut pathNfilename) { - OK_TO_CONTINUE = false; - } else { - (*msg).param.set(Param::File, as_str(pathNfilename)); - if (*msg).type_0 == Viewtype::File || (*msg).type_0 == Viewtype::Image { - /* Correct the type, take care not to correct already very special formats as GIF or VOICE. - Typical conversions: - - from FILE to AUDIO/VIDEO/IMAGE - - from FILE/IMAGE to GIF */ - - if let Some((type_, mime)) = - dc_msg_guess_msgtype_from_suffix(as_path(pathNfilename)) - { - (*msg).type_0 = type_; - (*msg).param.set(Param::MimeType, mime); - } - } else if !(*msg).param.exists(Param::MimeType) { - if let Some((_, mime)) = dc_msg_guess_msgtype_from_suffix(as_path(pathNfilename)) { - (*msg).param.set(Param::MimeType, mime); - } - } - info!( - context, - 0, - "Attaching \"{}\" for message type #{}.", - as_str(pathNfilename), - (*msg).type_0 - ); - - free(pathNfilename as *mut _); - } - } else { - error!( - context, - 0, - "Cannot send messages of type #{}.", - (*msg).type_0 - ); - OK_TO_CONTINUE = false; - } - if OK_TO_CONTINUE { - dc_unarchive_chat(context, chat_id); - let chat = dc_chat_new(context); - if dc_chat_load_from_db(chat, chat_id) { - if (*msg).state != DC_STATE_OUT_PREPARING { - (*msg).state = DC_STATE_OUT_PENDING - } - (*msg).id = prepare_msg_raw(context, chat, msg, dc_create_smeared_timestamp(context)); - (*msg).chat_id = chat_id - } - dc_chat_unref(chat); - } - - (*msg).id -} - -#[allow(non_snake_case)] -unsafe fn prepare_msg_raw( - context: &Context, - chat: *mut Chat, - msg: *mut dc_msg_t, - timestamp: i64, -) -> uint32_t { - let mut do_guarantee_e2ee: libc::c_int; - let e2ee_enabled: libc::c_int; - let mut OK_TO_CONTINUE = true; - let mut parent_rfc724_mid = ptr::null_mut(); - let mut parent_references = ptr::null_mut(); - let mut parent_in_reply_to = ptr::null_mut(); - let mut new_rfc724_mid = ptr::null_mut(); - let mut new_references = ptr::null_mut(); - let mut new_in_reply_to = ptr::null_mut(); - let mut msg_id = 0; - let mut to_id = 0; - let mut location_id = 0; - - if !((*chat).type_0 == 100 || (*chat).type_0 == 120 || (*chat).type_0 == 130) { - error!(context, 0, "Cannot send to chat type #{}.", (*chat).type_0,); - } else if ((*chat).type_0 == 120 || (*chat).type_0 == 130) - && 0 == dc_is_contact_in_chat(context, (*chat).id, 1 as uint32_t) - { - log_event!( - context, - Event::ERROR_SELF_NOT_IN_GROUP, - 0, - "Cannot send message; self not in group.", - ); - } else { - let from = context.sql.get_config(context, "configured_addr"); - if from.is_none() { - error!(context, 0, "Cannot send message, not configured.",); - } else { - let from_c = CString::yolo(from.unwrap()); - new_rfc724_mid = dc_create_outgoing_rfc724_mid( - if (*chat).type_0 == 120 || (*chat).type_0 == 130 { - (*chat).grpid - } else { - ptr::null_mut() - }, - from_c.as_ptr(), - ); - - if (*chat).type_0 == DC_CHAT_TYPE_SINGLE { - if let Some(id) = context.sql.query_row_col( - context, - "SELECT contact_id FROM chats_contacts WHERE chat_id=?;", - params![(*chat).id as i32], - 0, - ) { - to_id = id; - } else { - error!( - context, - 0, - "Cannot send message, contact for chat #{} not found.", - (*chat).id, - ); - OK_TO_CONTINUE = false; - } - } else { - if (*chat).type_0 == DC_CHAT_TYPE_GROUP - || (*chat).type_0 == DC_CHAT_TYPE_VERIFIED_GROUP - { - if (*chat).param.get_int(Param::Unpromoted).unwrap_or_default() == 1 { - (*chat).param.remove(Param::Unpromoted); - dc_chat_update_param(chat); - } - } - } - if OK_TO_CONTINUE { - /* check if we can guarantee E2EE for this message. - if we guarantee E2EE, and circumstances change - so that E2EE is no longer available at a later point (reset, changed settings), - we do not send the message out at all */ - do_guarantee_e2ee = 0; - e2ee_enabled = context - .sql - .get_config_int(context, "e2ee_enabled") - .unwrap_or_else(|| 1); - if 0 != e2ee_enabled - && (*msg) - .param - .get_int(Param::ForcePlaintext) - .unwrap_or_default() - == 0 - { - let mut can_encrypt = 1; - let mut all_mutual = 1; - - let res = context.sql.query_row( - "SELECT ps.prefer_encrypted, c.addr \ - FROM chats_contacts cc \ - LEFT JOIN contacts c ON cc.contact_id=c.id \ - LEFT JOIN acpeerstates ps ON c.addr=ps.addr \ - WHERE cc.chat_id=? AND cc.contact_id>9;", - params![(*chat).id], - |row| { - let state: String = row.get(1)?; - - if let Some(prefer_encrypted) = row.get::<_, Option>(0)? { - if prefer_encrypted != 1 { - info!( - context, - 0, - "[autocrypt] peerstate for {} is {}", - state, - if prefer_encrypted == 0 { - "NOPREFERENCE" - } else { - "RESET" - }, - ); - all_mutual = 0; - } - } else { - info!(context, 0, "[autocrypt] no peerstate for {}", state,); - can_encrypt = 0; - all_mutual = 0; - } - Ok(()) - }, - ); - match res { - Ok(_) => {} - Err(err) => { - warn!(context, 0, "chat: failed to load peerstates: {:?}", err); - } - } - - if 0 != can_encrypt { - if 0 != all_mutual { - do_guarantee_e2ee = 1; - } else if 0 != last_msg_in_chat_encrypted(context, &context.sql, (*chat).id) - { - do_guarantee_e2ee = 1; - } - } - } - if 0 != do_guarantee_e2ee { - (*msg).param.set_int(Param::GuranteeE2ee, 1); - } - (*msg).param.remove(Param::ErroneousE2ee); - if 0 == dc_chat_is_self_talk(chat) - && 0 != get_parent_mime_headers( - chat, - &mut parent_rfc724_mid, - &mut parent_in_reply_to, - &mut parent_references, - ) - { - if !parent_rfc724_mid.is_null() - && 0 != *parent_rfc724_mid.offset(0isize) as libc::c_int - { - new_in_reply_to = dc_strdup(parent_rfc724_mid) - } - if !parent_references.is_null() { - let space: *mut libc::c_char; - space = strchr(parent_references, ' ' as i32); - if !space.is_null() { - *space = 0 as libc::c_char - } - } - if !parent_references.is_null() - && 0 != *parent_references.offset(0isize) as libc::c_int - && !parent_rfc724_mid.is_null() - && 0 != *parent_rfc724_mid.offset(0isize) as libc::c_int - { - new_references = dc_mprintf( - b"%s %s\x00" as *const u8 as *const libc::c_char, - parent_references, - parent_rfc724_mid, - ) - } else if !parent_references.is_null() - && 0 != *parent_references.offset(0isize) as libc::c_int - { - new_references = dc_strdup(parent_references) - } else if !parent_in_reply_to.is_null() - && 0 != *parent_in_reply_to.offset(0isize) as libc::c_int - && !parent_rfc724_mid.is_null() - && 0 != *parent_rfc724_mid.offset(0isize) as libc::c_int - { - new_references = dc_mprintf( - b"%s %s\x00" as *const u8 as *const libc::c_char, - parent_in_reply_to, - parent_rfc724_mid, - ) - } else if !parent_in_reply_to.is_null() - && 0 != *parent_in_reply_to.offset(0isize) as libc::c_int - { - new_references = dc_strdup(parent_in_reply_to) - } - } - - // add independent location to database - - if (*msg).param.exists(Param::SetLatitude) { - if sql::execute( - context, - &context.sql, - "INSERT INTO locations \ - (timestamp,from_id,chat_id, latitude,longitude,independent)\ - VALUES (?,?,?, ?,?,1);", - params![ - timestamp, - DC_CONTACT_ID_SELF as i32, - (*chat).id as i32, - (*msg) - .param - .get_float(Param::SetLatitude) - .unwrap_or_default(), - (*msg) - .param - .get_float(Param::SetLongitude) - .unwrap_or_default(), - ], - ) - .is_ok() - { - location_id = sql::get_rowid2( - context, - &context.sql, - "locations", - "timestamp", - timestamp, - "from_id", - DC_CONTACT_ID_SELF as i32, - ); - } - } - - // add message to the database - - if sql::execute( - context, - &context.sql, - "INSERT INTO msgs (rfc724_mid, chat_id, from_id, to_id, timestamp, type, state, txt, param, hidden, mime_in_reply_to, mime_references, location_id) VALUES (?,?,?,?,?, ?,?,?,?,?, ?,?,?);", - params![ - as_str(new_rfc724_mid), - (*chat).id as i32, - 1i32, - to_id as i32, - timestamp, - (*msg).type_0, - (*msg).state, - (*msg).text, - (*msg).param.to_string(), - (*msg).hidden, - to_string(new_in_reply_to), - to_string(new_references), - location_id as i32, - ] - ).is_ok() { - msg_id = sql::get_rowid( - context, - &context.sql, - "msgs", - "rfc724_mid", - as_str(new_rfc724_mid), - ); - } else { - error!( - context, - 0, - "Cannot send message, cannot insert to database (chat #{}).", - (*chat).id, - ); - } - } - } - } - - free(parent_rfc724_mid as *mut libc::c_void); - free(parent_in_reply_to as *mut libc::c_void); - free(parent_references as *mut libc::c_void); - free(new_rfc724_mid as *mut libc::c_void); - free(new_in_reply_to as *mut libc::c_void); - free(new_references as *mut libc::c_void); - - msg_id -} - -// TODO should return bool /rtn -unsafe fn get_parent_mime_headers( - chat: *const Chat, - parent_rfc724_mid: *mut *mut libc::c_char, - parent_in_reply_to: *mut *mut libc::c_char, - parent_references: *mut *mut libc::c_char, -) -> libc::c_int { - let mut success = 0; - - if !(chat.is_null() - || parent_rfc724_mid.is_null() - || parent_in_reply_to.is_null() - || parent_references.is_null()) - { - // prefer a last message that isn't from us - success = (*chat) - .context - .sql - .query_row( - "SELECT rfc724_mid, mime_in_reply_to, mime_references \ - FROM msgs WHERE chat_id=?1 AND timestamp=(SELECT max(timestamp) \ - FROM msgs WHERE chat_id=?1 AND from_id!=?2);", - params![(*chat).id as i32, DC_CONTACT_ID_SELF as i32], - |row| { - *parent_rfc724_mid = row.get::<_, String>(0)?.strdup(); - *parent_in_reply_to = row.get::<_, String>(1)?.strdup(); - *parent_references = row.get::<_, String>(2)?.strdup(); - Ok(()) - }, - ) - .is_ok() as libc::c_int; - - if 0 == success { - success = (*chat) - .context - .sql - .query_row( - "SELECT rfc724_mid, mime_in_reply_to, mime_references \ - FROM msgs WHERE chat_id=?1 AND timestamp=(SELECT min(timestamp) \ - FROM msgs WHERE chat_id=?1 AND from_id==?2);", - params![(*chat).id as i32, DC_CONTACT_ID_SELF as i32], - |row| { - *parent_rfc724_mid = row.get::<_, String>(0)?.strdup(); - *parent_in_reply_to = row.get::<_, String>(1)?.strdup(); - *parent_references = row.get::<_, String>(2)?.strdup(); - Ok(()) - }, - ) - .is_ok() as libc::c_int; - } - } - success -} - -pub unsafe fn dc_chat_is_self_talk(chat: *const Chat) -> libc::c_int { - if chat.is_null() { - return 0; - } - (*chat).param.exists(Param::Selftalk) as libc::c_int -} - -/******************************************************************************* - * Sending messages - ******************************************************************************/ -// TODO should return bool /rtn -unsafe fn last_msg_in_chat_encrypted( - context: &Context, - sql: &Sql, - chat_id: uint32_t, -) -> libc::c_int { - let packed: Option = sql.query_row_col( - context, - "SELECT param \ - FROM msgs WHERE timestamp=(SELECT MAX(timestamp) FROM msgs WHERE chat_id=?) \ - ORDER BY id DESC;", - params![chat_id as i32], - 0, - ); - - if let Some(ref packed) = packed { - match packed.parse::() { - Ok(param) => param.exists(Param::GuranteeE2ee) as libc::c_int, - Err(err) => { - error!(context, 0, "invalid params stored: '{}', {:?}", packed, err); - 0 - } - } - } else { - 0 - } -} - -// TODO should return bool /rtn -pub unsafe fn dc_chat_update_param(chat: *mut Chat) -> libc::c_int { - sql::execute( - (*chat).context, - &(*chat).context.sql, - "UPDATE chats SET param=? WHERE id=?", - params![(*chat).param.to_string(), (*chat).id as i32], - ) - .is_ok() as libc::c_int -} - -pub unsafe fn dc_is_contact_in_chat( - context: &Context, - chat_id: uint32_t, - contact_id: uint32_t, -) -> libc::c_int { - /* this function works for group and for normal chats, however, it is more useful for group chats. - DC_CONTACT_ID_SELF may be used to check, if the user itself is in a group chat (DC_CONTACT_ID_SELF is not added to normal chats) */ - - context - .sql - .exists( - "SELECT contact_id FROM chats_contacts WHERE chat_id=? AND contact_id=?;", - params![chat_id as i32, contact_id as i32], - ) - .unwrap_or_default() as libc::c_int -} - -// Should return Result -pub fn dc_unarchive_chat(context: &Context, chat_id: u32) { - sql::execute( - context, - &context.sql, - "UPDATE chats SET archived=0 WHERE id=?", - params![chat_id as i32], - ) - .ok(); -} - -pub unsafe fn dc_send_msg<'a>( - context: &'a Context, - chat_id: uint32_t, - msg: *mut dc_msg_t<'a>, -) -> uint32_t { - if msg.is_null() { - return 0; - } - if (*msg).state != DC_STATE_OUT_PREPARING { - if 0 == prepare_msg_common(context, chat_id, msg) { - return 0; - } - } else { - if chat_id != 0 && chat_id != (*msg).chat_id { - return 0; - } - dc_update_msg_state(context, (*msg).id, DC_STATE_OUT_PENDING); - } - if 0 == dc_job_send_msg(context, (*msg).id) { - return 0; - } - context.call_cb( - Event::MSGS_CHANGED, - (*msg).chat_id as uintptr_t, - (*msg).id as uintptr_t, - ); - - if (*msg).param.exists(Param::SetLatitude) { - context.call_cb(Event::LOCATION_CHANGED, DC_CONTACT_ID_SELF, 0); - } - - if 0 == chat_id { - let forwards = (*msg).param.get(Param::PrepForwards); - if let Some(forwards) = forwards { - for forward in forwards.split(' ') { - let id: i32 = forward.parse().unwrap_or_default(); - if 0 == id { - // avoid hanging if user tampers with db - break; - } else { - let copy = dc_get_msg(context, id as u32); - if !copy.is_null() { - dc_send_msg(context, 0, copy); - } - dc_msg_unref(copy); - } - } - (*msg).param.remove(Param::PrepForwards); - dc_msg_save_param_to_disk(msg); - } - } - - (*msg).id -} - -pub unsafe fn dc_send_text_msg( - context: &Context, - chat_id: uint32_t, - text_to_send: String, -) -> uint32_t { - if chat_id <= 9 { - warn!( - context, - 0, "dc_send_text_msg: bad chat_id = {} <= 9", chat_id - ); - return 0; - } - - let mut msg = dc_msg_new(context, Viewtype::Text); - (*msg).text = Some(text_to_send); - let ret = dc_send_msg(context, chat_id, msg); - dc_msg_unref(msg); - ret -} - -pub unsafe fn dc_set_draft(context: &Context, chat_id: uint32_t, msg: *mut dc_msg_t) { - if chat_id <= 9i32 as libc::c_uint { - return; - } - if 0 != set_draft_raw(context, chat_id, msg) { - context.call_cb(Event::MSGS_CHANGED, chat_id as uintptr_t, 0i32 as uintptr_t); - }; -} - -// TODO should return bool /rtn -#[allow(non_snake_case)] -unsafe fn set_draft_raw(context: &Context, chat_id: uint32_t, msg: *mut dc_msg_t) -> libc::c_int { - let mut OK_TO_CONTINUE = true; - // similar to as dc_set_draft() but does not emit an event - let prev_draft_msg_id: uint32_t; - let mut sth_changed: libc::c_int = 0i32; - prev_draft_msg_id = get_draft_msg_id(context, chat_id); - if 0 != prev_draft_msg_id { - dc_delete_msg_from_db(context, prev_draft_msg_id); - sth_changed = 1i32 - } - // save new draft - if !msg.is_null() { - if (*msg).type_0 == Viewtype::Text { - OK_TO_CONTINUE = (*msg).text.as_ref().map_or(false, |s| !s.is_empty()); - } else if msgtype_has_file((*msg).type_0) { - let mut pathNfilename = (*msg) - .param - .get(Param::File) - .map(|s| s.strdup()) - .unwrap_or_else(|| std::ptr::null_mut()); - if pathNfilename.is_null() { - OK_TO_CONTINUE = false; - } else if 0 != dc_msg_is_increation(msg) && !dc_is_blobdir_path(context, pathNfilename) - { - OK_TO_CONTINUE = false; - } else if !dc_make_rel_and_copy(context, &mut pathNfilename) { - OK_TO_CONTINUE = false; - } else { - (*msg).param.set(Param::File, as_str(pathNfilename)); - } - free(pathNfilename as *mut _); - } else { - OK_TO_CONTINUE = false; - } - if OK_TO_CONTINUE { - if sql::execute( - context, - &context.sql, - "INSERT INTO msgs (chat_id, from_id, timestamp, type, state, txt, param, hidden) \ - VALUES (?,?,?, ?,?,?,?,?);", - params![ - chat_id as i32, - 1, - time(), - (*msg).type_0, - DC_STATE_OUT_DRAFT, - (*msg).text.as_ref().map(String::as_str).unwrap_or(""), - (*msg).param.to_string(), - 1, - ], - ) - .is_ok() - { - sth_changed = 1; - } - } - } - - sth_changed -} - -fn get_draft_msg_id(context: &Context, chat_id: u32) -> u32 { - let draft_msg_id: i32 = context - .sql - .query_row_col( - context, - "SELECT id FROM msgs WHERE chat_id=? AND state=?;", - params![chat_id as i32, DC_STATE_OUT_DRAFT], - 0, - ) - .unwrap_or_default(); - - draft_msg_id as u32 -} - -pub unsafe fn dc_get_draft(context: &Context, chat_id: uint32_t) -> *mut dc_msg_t { - let draft_msg_id: uint32_t; - let draft_msg: *mut dc_msg_t; - if chat_id <= 9i32 as libc::c_uint { - return 0 as *mut dc_msg_t; - } - draft_msg_id = get_draft_msg_id(context, chat_id); - if draft_msg_id == 0i32 as libc::c_uint { - return ptr::null_mut(); - } - draft_msg = dc_msg_new_untyped(context); - if !dc_msg_load_from_db(draft_msg, context, draft_msg_id) { - dc_msg_unref(draft_msg); - return ptr::null_mut(); - } - - draft_msg -} - -pub fn dc_get_chat_msgs( - context: &Context, - chat_id: uint32_t, - flags: uint32_t, - marker1before: uint32_t, -) -> *mut dc_array_t { - let mut ret = Vec::new(); - - let mut last_day = 0; - let cnv_to_local = dc_gm2local_offset(); - - let process_row = |row: &rusqlite::Row| Ok((row.get::<_, i32>(0)?, row.get::<_, i64>(1)?)); - let process_rows = |rows: rusqlite::MappedRows<_>| { - for row in rows { - let (curr_id, ts) = row?; - if curr_id as u32 == marker1before { - ret.push(DC_MSG_ID_MARKER1 as u32); - } - if 0 != flags & 0x1 { - let curr_local_timestamp = ts + cnv_to_local; - let curr_day = (curr_local_timestamp / 86400) as libc::c_int; - if curr_day != last_day { - ret.push(DC_MSG_ID_LAST_SPECIAL as u32); - last_day = curr_day; - } - } - ret.push(curr_id as u32); - } - Ok(()) - }; - - let success = if chat_id == 1 { - let show_emails = context - .sql - .get_config_int(context, "show_emails") - .unwrap_or_default(); - context.sql.query_map( - "SELECT m.id, m.timestamp FROM msgs m \ - LEFT JOIN chats ON m.chat_id=chats.id \ - LEFT JOIN contacts ON m.from_id=contacts.id WHERE m.from_id!=1 \ - AND m.from_id!=2 \ - AND m.hidden=0 \ - AND chats.blocked=2 \ - AND contacts.blocked=0 \ - AND m.msgrmsg>=? \ - ORDER BY m.timestamp,m.id;", - params![if show_emails == 2 { 0 } else { 1 }], - process_row, - process_rows, - ) - } else if chat_id == 5 { - context.sql.query_map( - "SELECT m.id, m.timestamp FROM msgs m \ - LEFT JOIN contacts ct ON m.from_id=ct.id WHERE m.starred=1 \ - AND m.hidden=0 \ - AND ct.blocked=0 \ - ORDER BY m.timestamp,m.id;", - params![], - process_row, - process_rows, - ) - } else { - context.sql.query_map( - "SELECT m.id, m.timestamp FROM msgs m \ - WHERE m.chat_id=? \ - AND m.hidden=0 \ - ORDER BY m.timestamp,m.id;", - params![chat_id as i32], - process_row, - process_rows, - ) - }; - - if success.is_ok() { - dc_array_t::from(ret).into_raw() - } else { - 0 as *mut dc_array_t - } -} - -pub fn dc_get_msg_cnt(context: &Context, chat_id: u32) -> libc::c_int { - context - .sql - .query_row_col( - context, - "SELECT COUNT(*) FROM msgs WHERE chat_id=?;", - params![chat_id as i32], - 0, - ) - .unwrap_or_default() -} - -pub fn dc_get_fresh_msg_cnt(context: &Context, chat_id: u32) -> libc::c_int { - context - .sql - .query_row_col( - context, - "SELECT COUNT(*) FROM msgs \ - WHERE state=10 \ - AND hidden=0 \ - AND chat_id=?;", - params![chat_id as i32], - 0, - ) - .unwrap_or_default() -} - -pub fn dc_marknoticed_chat(context: &Context, chat_id: u32) -> bool { - if !context - .sql - .exists( - "SELECT id FROM msgs WHERE chat_id=? AND state=?;", - params![chat_id as i32, DC_STATE_IN_FRESH], - ) - .unwrap_or_default() - { - return false; - } - if sql::execute( - context, - &context.sql, - "UPDATE msgs \ - SET state=13 WHERE chat_id=? AND state=10;", - params![chat_id as i32], - ) - .is_err() - { - return false; - } - context.call_cb(Event::MSGS_CHANGED, 0 as uintptr_t, 0 as uintptr_t); - true -} - -pub fn dc_marknoticed_all_chats(context: &Context) -> bool { - if !context - .sql - .exists( - "SELECT id FROM msgs \ - WHERE state=10;", - params![], - ) - .unwrap_or_default() - { - return false; - } - - if sql::execute( - context, - &context.sql, - "UPDATE msgs \ - SET state=13 WHERE state=10;", - params![], - ) - .is_err() - { - return false; - } - - context.call_cb(Event::MSGS_CHANGED, 0 as uintptr_t, 0 as uintptr_t); - - true -} - -pub fn dc_get_chat_media( - context: &Context, - chat_id: uint32_t, - msg_type: Viewtype, - msg_type2: Viewtype, - msg_type3: Viewtype, -) -> *mut dc_array_t { - context.sql.query_map( - "SELECT id FROM msgs WHERE chat_id=? AND (type=? OR type=? OR type=?) ORDER BY timestamp, id;", - params![ - chat_id as i32, - msg_type, - if msg_type2 != Viewtype::Unknown { - msg_type2 - } else { - msg_type - }, if msg_type3 != Viewtype::Unknown { - msg_type3 - } else { - msg_type - }, - ], - |row| row.get::<_, i32>(0), - |ids| { - let mut ret = Vec::new(); - for id in ids { - ret.push(id? as u32); - } - Ok(dc_array_t::from(ret).into_raw()) - } - ).unwrap_or_else(|_| std::ptr::null_mut()) -} - -pub unsafe fn dc_get_next_media( - context: &Context, - curr_msg_id: uint32_t, - dir: libc::c_int, - msg_type: Viewtype, - msg_type2: Viewtype, - msg_type3: Viewtype, -) -> uint32_t { - let mut ret_msg_id: uint32_t = 0i32 as uint32_t; - let msg: *mut dc_msg_t = dc_msg_new_untyped(context); - let mut list: *mut dc_array_t = ptr::null_mut(); - let mut i: libc::c_int; - let cnt: libc::c_int; - - if dc_msg_load_from_db(msg, context, curr_msg_id) { - list = dc_get_chat_media( - context, - (*msg).chat_id, - if msg_type != Viewtype::Unknown { - msg_type - } else { - (*msg).type_0 - }, - msg_type2, - msg_type3, - ); - if !list.is_null() { - cnt = dc_array_get_cnt(list) as libc::c_int; - i = 0i32; - while i < cnt { - if curr_msg_id == dc_array_get_id(list, i as size_t) { - if dir > 0i32 { - if i + 1i32 < cnt { - ret_msg_id = dc_array_get_id(list, (i + 1i32) as size_t) - } - } else if dir < 0i32 { - if i - 1i32 >= 0i32 { - ret_msg_id = dc_array_get_id(list, (i - 1i32) as size_t) - } - } - break; - } else { - i += 1 - } - } - } - } - - if !list.is_null() { - dc_array_unref(list); - } - dc_msg_unref(msg); - ret_msg_id -} - -pub fn dc_archive_chat(context: &Context, chat_id: u32, archive: libc::c_int) -> bool { - if chat_id <= 9 || archive != 0 && archive != 1 { - return true; - } - if 0 != archive { - if sql::execute( - context, - &context.sql, - "UPDATE msgs SET state=? WHERE chat_id=? AND state=?;", - params![DC_STATE_IN_NOTICED, chat_id as i32, DC_STATE_IN_FRESH], - ) - .is_err() - { - return false; - } - } - if sql::execute( - context, - &context.sql, - "UPDATE chats SET archived=? WHERE id=?;", - params![archive, chat_id as i32], - ) - .is_err() - { - return false; - } - context.call_cb(Event::MSGS_CHANGED, 0 as uintptr_t, 0 as uintptr_t); - - true -} - -pub fn dc_delete_chat(context: &Context, chat_id: u32) -> bool { - /* Up to 2017-11-02 deleting a group also implied leaving it, see above why we have changed this. */ - if chat_id <= 9 { - return false; - } - let obj = unsafe { dc_chat_new(context) }; - if !dc_chat_load_from_db(obj, chat_id) { - return false; - } - unsafe { dc_chat_unref(obj) }; - - if sql::execute( - context, - &context.sql, - "DELETE FROM msgs_mdns WHERE msg_id IN (SELECT id FROM msgs WHERE chat_id=?);", - params![chat_id as i32], - ) - .is_err() - { - return false; - } - if sql::execute( - context, - &context.sql, - "DELETE FROM msgs WHERE chat_id=?;", - params![chat_id as i32], - ) - .is_err() - { - return false; - } - if sql::execute( - context, - &context.sql, - "DELETE FROM chats_contacts WHERE chat_id=?;", - params![chat_id as i32], - ) - .is_err() - { - return false; - } - if sql::execute( - context, - &context.sql, - "DELETE FROM chats WHERE id=?;", - params![chat_id as i32], - ) - .is_err() - { - return false; - } - - context.call_cb(Event::MSGS_CHANGED, 0 as uintptr_t, 0 as uintptr_t); - - dc_job_kill_action(context, 105); - unsafe { dc_job_add(context, 105, 0, Params::new(), 10) }; - - true -} - -pub fn dc_get_chat_contacts(context: &Context, chat_id: u32) -> Vec { - /* Normal chats do not include SELF. Group chats do (as it may happen that one is deleted from a - groupchat but the chats stays visible, moreover, this makes displaying lists easier) */ - - if chat_id == 1 { - return Vec::new(); - } - - // we could also create a list for all contacts in the deaddrop by searching contacts belonging to chats with - // chats.blocked=2, however, currently this is not needed - - context - .sql - .query_map( - "SELECT cc.contact_id FROM chats_contacts cc \ - LEFT JOIN contacts c ON c.id=cc.contact_id WHERE cc.chat_id=? \ - ORDER BY c.id=1, LOWER(c.name||c.addr), c.id;", - params![chat_id], - |row| row.get::<_, u32>(0), - |ids| ids.collect::, _>>().map_err(Into::into), - ) - .unwrap_or_default() -} - -pub unsafe fn dc_get_chat(context: &Context, chat_id: uint32_t) -> *mut Chat { - let mut success: libc::c_int = 0i32; - let obj: *mut Chat = dc_chat_new(context); - - if dc_chat_load_from_db(obj, chat_id) { - success = 1i32 - } - - if 0 != success { - obj - } else { - dc_chat_unref(obj); - ptr::null_mut() - } -} - -// handle group chats -pub unsafe fn dc_create_group_chat( - context: &Context, - verified: libc::c_int, - chat_name: *const libc::c_char, -) -> u32 { - let mut chat_id = 0; - - if chat_name.is_null() || *chat_name.offset(0) as libc::c_int == 0 { - return 0; - } - let draft_txt = - CString::new(context.stock_string_repl_str(StockMessage::NewGroupDraft, as_str(chat_name))) - .unwrap(); - let grpid = dc_create_id(); - if sql::execute( - context, - &context.sql, - "INSERT INTO chats (type, name, grpid, param) VALUES(?, ?, ?, \'U=1\');", - params![ - if verified != 0 { 130 } else { 120 }, - as_str(chat_name), - grpid - ], - ) - .is_ok() - { - chat_id = sql::get_rowid(context, &context.sql, "chats", "grpid", grpid); - if chat_id != 0 { - if 0 != dc_add_to_chat_contacts_table(context, chat_id, 1) { - let draft_msg = dc_msg_new(context, Viewtype::Text); - dc_msg_set_text(draft_msg, draft_txt.as_ptr()); - set_draft_raw(context, chat_id, draft_msg); - dc_msg_unref(draft_msg); - } - } - } - if 0 != chat_id { - context.call_cb(Event::MSGS_CHANGED, 0 as uintptr_t, 0 as uintptr_t); - } - - chat_id -} - -/* you MUST NOT modify this or the following strings */ -// Context functions to work with chats -// TODO should return bool /rtn -pub fn dc_add_to_chat_contacts_table( - context: &Context, - chat_id: u32, - contact_id: u32, -) -> libc::c_int { - // add a contact to a chat; the function does not check the type or if any of the record exist or are already - // added to the chat! - sql::execute( - context, - &context.sql, - "INSERT INTO chats_contacts (chat_id, contact_id) VALUES(?, ?)", - params![chat_id as i32, contact_id as i32], - ) - .is_ok() as libc::c_int -} - -pub unsafe fn dc_add_contact_to_chat( - context: &Context, - chat_id: u32, - contact_id: u32, -) -> libc::c_int { - dc_add_contact_to_chat_ex(context, chat_id, contact_id, 0) -} - -// TODO should return bool /rtn -#[allow(non_snake_case)] -pub unsafe fn dc_add_contact_to_chat_ex( - context: &Context, - chat_id: u32, - contact_id: u32, - flags: libc::c_int, -) -> libc::c_int { - let mut OK_TO_CONTINUE = true; - let mut success: libc::c_int = 0; - let contact = Contact::get_by_id(context, contact_id); - let chat: *mut Chat = dc_chat_new(context); - let mut msg: *mut dc_msg_t = dc_msg_new_untyped(context); - - if !(contact.is_err() || chat_id <= 9 as libc::c_uint) { - dc_reset_gossiped_timestamp(context, chat_id); - let contact = contact.unwrap(); - - /*this also makes sure, not contacts are added to special or normal chats*/ - if !(0 == real_group_exists(context, chat_id) - || !Contact::real_exists_by_id(context, contact_id) - && contact_id != DC_CONTACT_ID_SELF as u32 - || !dc_chat_load_from_db(chat, chat_id)) - { - if !(dc_is_contact_in_chat(context, chat_id, 1 as uint32_t) == 1) { - log_event!( - context, - Event::ERROR_SELF_NOT_IN_GROUP, - 0, - "Cannot add contact to group; self not in group.", - ); - } else { - /* we should respect this - whatever we send to the group, it gets discarded anyway! */ - if 0 != flags & 0x1 - && (*chat).param.get_int(Param::Unpromoted).unwrap_or_default() == 1 - { - (*chat).param.remove(Param::Unpromoted); - dc_chat_update_param(chat); - } - let self_addr = context - .sql - .get_config(context, "configured_addr") - .unwrap_or_default(); - if contact.get_addr() != &self_addr { - // ourself is added using DC_CONTACT_ID_SELF, do not add it explicitly. - // if SELF is not in the group, members cannot be added at all. - - if 0 != dc_is_contact_in_chat(context, chat_id, contact_id) { - if 0 == flags & 0x1 { - success = 1; - OK_TO_CONTINUE = false; - } - } else { - // else continue and send status mail - if (*chat).type_0 == 130 { - if contact.is_verified() != VerifiedStatus::BidirectVerified { - error!( - context, 0, - "Only bidirectional verified contacts can be added to verified groups." - ); - OK_TO_CONTINUE = false; - } - } - if OK_TO_CONTINUE { - if 0 == dc_add_to_chat_contacts_table(context, chat_id, contact_id) { - OK_TO_CONTINUE = false; - } - } - } - if OK_TO_CONTINUE { - if (*chat).param.get_int(Param::Unpromoted).unwrap_or_default() == 0 { - (*msg).type_0 = Viewtype::Text; - (*msg).text = Some(context.stock_system_msg( - StockMessage::MsgAddMember, - contact.get_addr(), - "", - DC_CONTACT_ID_SELF as uint32_t, - )); - (*msg).param.set_int(Param::Cmd, 4); - (*msg).param.set(Param::Arg, contact.get_addr()); - (*msg).param.set_int(Param::Arg2, flags); - (*msg).id = dc_send_msg(context, chat_id, msg); - context.call_cb( - Event::MSGS_CHANGED, - chat_id as uintptr_t, - (*msg).id as uintptr_t, - ); - } - context.call_cb(Event::MSGS_CHANGED, chat_id as uintptr_t, 0 as uintptr_t); - success = 1; - } - } - } - } - } - dc_chat_unref(chat); - dc_msg_unref(msg); - - success -} - -// TODO should return bool /rtn -fn real_group_exists(context: &Context, chat_id: u32) -> libc::c_int { - // check if a group or a verified group exists under the given ID - if !context.sql.is_open() || chat_id <= 9 { - return 02; - } - - context - .sql - .exists( - "SELECT id FROM chats WHERE id=? AND (type=120 OR type=130);", - params![chat_id as i32], - ) - .unwrap_or_default() as libc::c_int -} - -pub fn dc_reset_gossiped_timestamp(context: &Context, chat_id: u32) { - dc_set_gossiped_timestamp(context, chat_id, 0); -} - -// Should return Result -pub fn dc_set_gossiped_timestamp(context: &Context, chat_id: u32, timestamp: i64) { - if 0 != chat_id { - info!( - context, - 0, "set gossiped_timestamp for chat #{} to {}.", chat_id, timestamp, - ); - - sql::execute( - context, - &context.sql, - "UPDATE chats SET gossiped_timestamp=? WHERE id=?;", - params![timestamp, chat_id as i32], - ) - .ok(); - } else { - info!( - context, - 0, "set gossiped_timestamp for all chats to {}.", timestamp, - ); - sql::execute( - context, - &context.sql, - "UPDATE chats SET gossiped_timestamp=?;", - params![timestamp], - ) - .ok(); - } -} - -// TODO should return bool /rtn -pub unsafe fn dc_remove_contact_from_chat( - context: &Context, - chat_id: u32, - contact_id: u32, -) -> libc::c_int { - let mut success = 0; - let chat: *mut Chat = dc_chat_new(context); - let mut msg: *mut dc_msg_t = dc_msg_new_untyped(context); - - if !(chat_id <= 9 as libc::c_uint - || contact_id <= 9 as libc::c_uint && contact_id != DC_CONTACT_ID_SELF as u32) - { - /* we do not check if "contact_id" exists but just delete all records with the id from chats_contacts */ - /* this allows to delete pending references to deleted contacts. Of course, this should _not_ happen. */ - if !(0 == real_group_exists(context, chat_id) || !dc_chat_load_from_db(chat, chat_id)) { - if !(dc_is_contact_in_chat(context, chat_id, 1 as uint32_t) == 1) { - log_event!( - context, - Event::ERROR_SELF_NOT_IN_GROUP, - 0, - "Cannot remove contact from chat; self not in group.", - ); - } else { - /* we should respect this - whatever we send to the group, it gets discarded anyway! */ - if let Ok(contact) = Contact::get_by_id(context, contact_id) { - if (*chat).param.get_int(Param::Unpromoted).unwrap_or_default() == 0 { - (*msg).type_0 = Viewtype::Text; - if contact.id == DC_CONTACT_ID_SELF as u32 { - dc_set_group_explicitly_left(context, (*chat).grpid); - (*msg).text = Some(context.stock_system_msg( - StockMessage::MsgGroupLeft, - "", - "", - DC_CONTACT_ID_SELF as u32, - )); - } else { - (*msg).text = Some(context.stock_system_msg( - StockMessage::MsgDelMember, - contact.get_addr(), - "", - DC_CONTACT_ID_SELF as u32, - )); - } - (*msg).param.set_int(Param::Cmd, 5); - (*msg).param.set(Param::Arg, contact.get_addr()); - (*msg).id = dc_send_msg(context, chat_id, msg); - context.call_cb( - Event::MSGS_CHANGED, - chat_id as uintptr_t, - (*msg).id as uintptr_t, - ); - } - } - if sql::execute( - context, - &context.sql, - "DELETE FROM chats_contacts WHERE chat_id=? AND contact_id=?;", - params![chat_id as i32, contact_id as i32], - ) - .is_ok() - { - context.call_cb(Event::CHAT_MODIFIED, chat_id as uintptr_t, 0 as uintptr_t); - success = 1; - } - } - } - } - - dc_chat_unref(chat); - dc_msg_unref(msg); - - success -} - -// Should return Result -fn dc_set_group_explicitly_left(context: &Context, grpid: *const libc::c_char) { - if 0 == dc_is_group_explicitly_left(context, grpid) { - sql::execute( - context, - &context.sql, - "INSERT INTO leftgrps (grpid) VALUES(?);", - params![as_str(grpid)], - ) - .ok(); - } -} - -// TODO should return bool /rtn -pub fn dc_is_group_explicitly_left(context: &Context, grpid: *const libc::c_char) -> libc::c_int { - context - .sql - .exists( - "SELECT id FROM leftgrps WHERE grpid=?;", - params![as_str(grpid)], - ) - .unwrap_or_default() as libc::c_int -} - -// TODO should return bool /rtn -pub unsafe fn dc_set_chat_name( - context: &Context, - chat_id: uint32_t, - new_name: *const libc::c_char, -) -> libc::c_int { - /* the function only sets the names of group chats; normal chats get their names from the contacts */ - let mut success: libc::c_int = 0i32; - let chat: *mut Chat = dc_chat_new(context); - let mut msg: *mut dc_msg_t = dc_msg_new_untyped(context); - - if !(new_name.is_null() - || *new_name.offset(0isize) as libc::c_int == 0i32 - || chat_id <= 9i32 as libc::c_uint) - { - if !(0i32 == real_group_exists(context, chat_id) || !dc_chat_load_from_db(chat, chat_id)) { - if strcmp((*chat).name, new_name) == 0i32 { - success = 1i32 - } else if !(dc_is_contact_in_chat(context, chat_id, 1i32 as uint32_t) == 1i32) { - log_event!( - context, - Event::ERROR_SELF_NOT_IN_GROUP, - 0, - "Cannot set chat name; self not in group", - ); - } else { - /* we should respect this - whatever we send to the group, it gets discarded anyway! */ - if sql::execute( - context, - &context.sql, - format!( - "UPDATE chats SET name='{}' WHERE id={};", - as_str(new_name), - chat_id as i32 - ), - params![], - ) - .is_ok() - { - if (*chat).param.get_int(Param::Unpromoted).unwrap_or_default() == 0 { - (*msg).type_0 = Viewtype::Text; - (*msg).text = Some(context.stock_system_msg( - StockMessage::MsgGrpName, - as_str((*chat).name), - as_str(new_name), - DC_CONTACT_ID_SELF as u32, - )); - (*msg).param.set_int(Param::Cmd, 2); - if !(*chat).name.is_null() { - (*msg).param.set(Param::Arg, as_str((*chat).name)); - } - (*msg).id = dc_send_msg(context, chat_id, msg); - context.call_cb( - Event::MSGS_CHANGED, - chat_id as uintptr_t, - (*msg).id as uintptr_t, - ); - } - context.call_cb( - Event::CHAT_MODIFIED, - chat_id as uintptr_t, - 0i32 as uintptr_t, - ); - success = 1i32 - } - } - } - } - - dc_chat_unref(chat); - dc_msg_unref(msg); - - success -} - -// TODO should return bool /rtn -#[allow(non_snake_case)] -pub unsafe fn dc_set_chat_profile_image( - context: &Context, - chat_id: uint32_t, - new_image: *const libc::c_char, -) -> libc::c_int { - let mut OK_TO_CONTINUE = true; - let mut success: libc::c_int = 0i32; - let chat: *mut Chat = dc_chat_new(context); - let mut msg: *mut dc_msg_t = dc_msg_new_untyped(context); - let mut new_image_rel: *mut libc::c_char = ptr::null_mut(); - if !(chat_id <= 9i32 as libc::c_uint) { - if !(0i32 == real_group_exists(context, chat_id) || !dc_chat_load_from_db(chat, chat_id)) { - if !(dc_is_contact_in_chat(context, chat_id, 1i32 as uint32_t) == 1i32) { - log_event!( - context, - Event::ERROR_SELF_NOT_IN_GROUP, - 0, - "Cannot set chat profile image; self not in group.", - ); - } else { - /* we should respect this - whatever we send to the group, it gets discarded anyway! */ - if !new_image.is_null() { - new_image_rel = dc_strdup(new_image); - if !dc_make_rel_and_copy(context, &mut new_image_rel) { - OK_TO_CONTINUE = false; - } - } else { - OK_TO_CONTINUE = false; - } - if OK_TO_CONTINUE { - (*chat) - .param - .set(Param::ProfileImage, as_str(new_image_rel)); - if !(0 == dc_chat_update_param(chat)) { - if (*chat).param.get_int(Param::Unpromoted).unwrap_or_default() == 0 { - (*msg).param.set_int(Param::Cmd, 3); - (*msg).param.set(Param::Arg, as_str(new_image_rel)); - (*msg).type_0 = Viewtype::Text; - (*msg).text = Some(context.stock_system_msg( - if !new_image_rel.is_null() { - StockMessage::MsgGrpImgChanged - } else { - StockMessage::MsgGrpImgDeleted - }, - "", - "", - DC_CONTACT_ID_SELF as uint32_t, - )); - (*msg).id = dc_send_msg(context, chat_id, msg); - context.call_cb( - Event::MSGS_CHANGED, - chat_id as uintptr_t, - (*msg).id as uintptr_t, - ); - } - context.call_cb( - Event::CHAT_MODIFIED, - chat_id as uintptr_t, - 0i32 as uintptr_t, - ); - success = 1i32 - } - } - } - } - } - - dc_chat_unref(chat); - dc_msg_unref(msg); - free(new_image_rel as *mut libc::c_void); - - success -} - -pub unsafe fn dc_forward_msgs( - context: &Context, - msg_ids: *const u32, - msg_cnt: libc::c_int, - chat_id: u32, -) { - if msg_ids.is_null() || msg_cnt <= 0 || chat_id <= 9 { - return; - } - - let msg = dc_msg_new_untyped(context); - let chat = dc_chat_new(context); - let mut created_db_entries = Vec::new(); - let mut curr_timestamp: i64; - - dc_unarchive_chat(context, chat_id); - if dc_chat_load_from_db(chat, chat_id) { - curr_timestamp = dc_create_smeared_timestamps(context, msg_cnt); - let idsstr = std::slice::from_raw_parts(msg_ids, msg_cnt as usize) - .iter() - .enumerate() - .fold( - String::with_capacity(2 * msg_cnt as usize), - |acc, (i, n)| (if i == 0 { acc } else { acc + "," }) + &n.to_string(), - ); - - let ids = context - .sql - .query_map( - format!( - "SELECT id FROM msgs WHERE id IN({}) ORDER BY timestamp,id", - idsstr - ), - params![], - |row| row.get::<_, i32>(0), - |ids| ids.collect::, _>>().map_err(Into::into), - ) - .unwrap(); // TODO: better error handling - - for id in ids { - let src_msg_id = id; - if !dc_msg_load_from_db(msg, context, src_msg_id as u32) { - break; - } - let original_param = (*msg).param.clone(); - if (*msg).from_id != DC_CONTACT_ID_SELF as u32 { - (*msg).param.set_int(Param::Forwarded, 1); - } - (*msg).param.remove(Param::GuranteeE2ee); - (*msg).param.remove(Param::ForcePlaintext); - (*msg).param.remove(Param::Cmd); - - let new_msg_id: uint32_t; - if (*msg).state == DC_STATE_OUT_PREPARING { - let fresh9 = curr_timestamp; - curr_timestamp = curr_timestamp + 1; - new_msg_id = prepare_msg_raw(context, chat, msg, fresh9); - let save_param = (*msg).param.clone(); - (*msg).param = original_param; - (*msg).id = src_msg_id as uint32_t; - - if let Some(old_fwd) = (*msg).param.get(Param::PrepForwards) { - let new_fwd = format!("{} {}", old_fwd, new_msg_id); - (*msg).param.set(Param::PrepForwards, new_fwd); - } else { - (*msg) - .param - .set(Param::PrepForwards, new_msg_id.to_string()); - } - - dc_msg_save_param_to_disk(msg); - (*msg).param = save_param; - } else { - (*msg).state = DC_STATE_OUT_PENDING; - let fresh10 = curr_timestamp; - curr_timestamp = curr_timestamp + 1; - new_msg_id = prepare_msg_raw(context, chat, msg, fresh10); - dc_job_send_msg(context, new_msg_id); - } - created_db_entries.push(chat_id); - created_db_entries.push(new_msg_id); - } - } - - for i in (0..created_db_entries.len()).step_by(2) { - context.call_cb( - Event::MSGS_CHANGED, - created_db_entries[i] as uintptr_t, - created_db_entries[i + 1] as uintptr_t, - ); - } - dc_msg_unref(msg); - dc_chat_unref(chat); -} - -pub unsafe fn dc_chat_get_id(chat: *const Chat) -> uint32_t { - if chat.is_null() { - return 0i32 as uint32_t; - } - (*chat).id -} - -pub unsafe fn dc_chat_get_type(chat: *const Chat) -> libc::c_int { - if chat.is_null() { - return 0i32; - } - (*chat).type_0 -} - -pub unsafe fn dc_chat_get_name(chat: *const Chat) -> *mut libc::c_char { - if chat.is_null() { - return dc_strdup(b"Err\x00" as *const u8 as *const libc::c_char); - } - dc_strdup((*chat).name) -} - -pub unsafe fn dc_chat_get_subtitle(chat: *const Chat) -> *mut libc::c_char { - /* returns either the address or the number of chat members */ - if chat.is_null() { - return dc_strdup(b"Err\x00" as *const u8 as *const libc::c_char); - } - - let mut ret: *mut libc::c_char = std::ptr::null_mut(); - if (*chat).type_0 == 100 && (*chat).param.exists(Param::Selftalk) { - ret = (*chat) - .context - .stock_str(StockMessage::SelfTalkSubTitle) - .strdup(); - } else if (*chat).type_0 == 100 { - let ret_raw: String = (*chat) - .context - .sql - .query_row_col( - (*chat).context, - "SELECT c.addr FROM chats_contacts cc \ - LEFT JOIN contacts c ON c.id=cc.contact_id \ - WHERE cc.chat_id=?;", - params![(*chat).id as i32], - 0, - ) - .unwrap_or_else(|| "Err".into()); - ret = ret_raw.strdup(); - } else if (*chat).type_0 == 120 || (*chat).type_0 == 130 { - if (*chat).id == 1 { - ret = (*chat).context.stock_str(StockMessage::DeadDrop).strdup(); - } else { - let cnt = dc_get_chat_contact_cnt((*chat).context, (*chat).id); - ret = (*chat) - .context - .stock_string_repl_int(StockMessage::Member, cnt) - .strdup(); - } - } - if !ret.is_null() { - ret - } else { - dc_strdup(b"Err\x00" as *const u8 as *const libc::c_char) - } -} - -pub fn dc_get_chat_contact_cnt(context: &Context, chat_id: u32) -> libc::c_int { - context - .sql - .query_row_col( - context, - "SELECT COUNT(*) FROM chats_contacts WHERE chat_id=?;", - params![chat_id as i32], - 0, - ) - .unwrap_or_default() -} - -pub unsafe fn dc_chat_get_profile_image(chat: *const Chat) -> *mut libc::c_char { - let mut image_rel: *mut libc::c_char = 0 as *mut libc::c_char; - let mut image_abs: *mut libc::c_char = 0 as *mut libc::c_char; - - if !chat.is_null() { - image_rel = (*chat) - .param - .get(Param::ProfileImage) - .unwrap_or_default() - .strdup(); - if !image_rel.is_null() && 0 != *image_rel.offset(0isize) as libc::c_int { - image_abs = dc_get_abs_path((*chat).context, image_rel) - } else if (*chat).type_0 == DC_CHAT_TYPE_SINGLE { - let contacts = dc_get_chat_contacts((*chat).context, (*chat).id); - if !contacts.is_empty() { - if let Ok(contact) = Contact::get_by_id((*chat).context, contacts[0]) { - if let Some(img) = contact.get_profile_image() { - image_abs = img.strdup(); - } - } - } - } - } - - free(image_rel as *mut libc::c_void); - - image_abs -} - -pub unsafe fn dc_chat_get_color(chat: *const Chat) -> uint32_t { - let mut color: uint32_t = 0i32 as uint32_t; - - if !chat.is_null() { - if (*chat).type_0 == DC_CHAT_TYPE_SINGLE { - let contacts = dc_get_chat_contacts((*chat).context, (*chat).id); - if !contacts.is_empty() { - if let Ok(contact) = Contact::get_by_id((*chat).context, contacts[0]) { - color = contact.get_color(); - } - } - } else { - color = dc_str_to_color((*chat).name) as uint32_t - } - } - - color -} - -// TODO should return bool /rtn -pub unsafe fn dc_chat_get_archived(chat: *const Chat) -> libc::c_int { - if chat.is_null() { - return 0i32; - } - (*chat).archived -} - -// TODO should return bool /rtn -pub unsafe fn dc_chat_is_unpromoted(chat: *const Chat) -> libc::c_int { - if chat.is_null() { - return 0; - } - (*chat).param.get_int(Param::Unpromoted).unwrap_or_default() as libc::c_int -} - -// TODO should return bool /rtn -pub unsafe fn dc_chat_is_verified(chat: *const Chat) -> libc::c_int { - if chat.is_null() { - return 0i32; - } - ((*chat).type_0 == 130i32) as libc::c_int -} - -// TODO should return bool /rtn -pub unsafe fn dc_chat_is_sending_locations(chat: *const Chat) -> libc::c_int { - if chat.is_null() { - return 0i32; - } - (*chat).is_sending_locations -} - -pub fn dc_get_chat_cnt(context: &Context) -> usize { - if context.sql.is_open() { - /* no database, no chats - this is no error (needed eg. for information) */ - context - .sql - .query_row_col::<_, isize>( - context, - "SELECT COUNT(*) FROM chats WHERE id>9 AND blocked=0;", - params![], - 0, - ) - .unwrap_or_default() as usize - } else { - 0 - } -} - -pub unsafe fn dc_get_chat_id_by_grpid( - context: &Context, - grpid: *const libc::c_char, - ret_blocked: *mut libc::c_int, - ret_verified: *mut libc::c_int, -) -> u32 { - if !ret_blocked.is_null() { - *ret_blocked = 0; - } - if !ret_verified.is_null() { - *ret_verified = 0; - } - - context - .sql - .query_row( - "SELECT id, blocked, type FROM chats WHERE grpid=?;", - params![as_str(grpid)], - |row| { - let chat_id = row.get(0)?; - if !ret_blocked.is_null() { - *ret_blocked = row.get(1)?; - } - if !ret_verified.is_null() { - let v: i32 = row.get(2)?; - *ret_verified = (v == 130) as libc::c_int; - } - Ok(chat_id) - }, - ) - .unwrap_or_default() -} - -pub fn dc_add_device_msg(context: &Context, chat_id: uint32_t, text: *const libc::c_char) { - if text.is_null() { - return; - } - let rfc724_mid = unsafe { - dc_create_outgoing_rfc724_mid( - ptr::null(), - b"@device\x00" as *const u8 as *const libc::c_char, - ) - }; - - if context.sql.execute( - "INSERT INTO msgs (chat_id,from_id,to_id, timestamp,type,state, txt,rfc724_mid) VALUES (?,?,?, ?,?,?, ?,?);", - params![ - chat_id as i32, - 2, - 2, - unsafe {dc_create_smeared_timestamp(context)}, - Viewtype::Text, - DC_STATE_IN_NOTICED, - as_str(text), - as_str(rfc724_mid), - ] - ).is_err() { - unsafe { free(rfc724_mid as *mut libc::c_void) }; - return; - } - - let msg_id = sql::get_rowid( - context, - &context.sql, - "msgs", - "rfc724_mid", - as_str(rfc724_mid), - ); - unsafe { free(rfc724_mid as *mut libc::c_void) }; - context.call_cb( - Event::MSGS_CHANGED, - chat_id as uintptr_t, - msg_id as uintptr_t, - ); -} diff --git a/src/dc_imex.rs b/src/dc_imex.rs index 4f8d80c3d..f15cf2ec9 100644 --- a/src/dc_imex.rs +++ b/src/dc_imex.rs @@ -6,10 +6,10 @@ use mmime::mmapstring::*; use mmime::other::*; use rand::{thread_rng, Rng}; +use crate::chat; use crate::config::Config; use crate::constants::*; use crate::context::Context; -use crate::dc_chat::*; use crate::dc_configure::*; use crate::dc_e2ee::*; use crate::dc_job::*; @@ -138,8 +138,7 @@ pub unsafe fn dc_initiate_key_transfer(context: &Context) -> *mut libc::c_char { setup_file_content_c.as_bytes().len(), )) { - let chat_id = dc_create_chat_by_contact_id(context, 1i32 as uint32_t); - if !(chat_id == 0i32 as libc::c_uint) { + if let Ok(chat_id) = chat::create_by_contact_id(context, 1) { msg = dc_msg_new_untyped(context); (*msg).type_0 = Viewtype::File; (*msg).param.set(Param::File, as_str(setup_file_name)); @@ -157,8 +156,7 @@ pub unsafe fn dc_initiate_key_transfer(context: &Context) -> *mut libc::c_char { .unwrap() .shall_stop_ongoing { - let msg_id = dc_send_msg(context, chat_id, msg); - if msg_id != 0 { + if let Ok(msg_id) = chat::send_msg(context, chat_id, msg) { dc_msg_unref(msg); msg = ptr::null_mut(); info!(context, 0, "Wait for setup message being sent ...",); @@ -970,7 +968,7 @@ unsafe fn import_self_keys(context: &Context, dir_name: *const libc::c_char) -> free(suffix as *mut libc::c_void); let name_f = entry.file_name(); let name_c = name_f.to_c_string().unwrap(); - suffix = dc_get_filesuffix_lc(name_c.as_ptr()); + suffix = dc_get_filesuffix_lc(name_f.to_string_lossy()); if suffix.is_null() || strcmp(suffix, b"asc\x00" as *const u8 as *const libc::c_char) != 0 { diff --git a/src/dc_job.rs b/src/dc_job.rs index 8da295ed5..fd17c285e 100644 --- a/src/dc_job.rs +++ b/src/dc_job.rs @@ -6,9 +6,9 @@ use std::time::Duration; use rand::{thread_rng, Rng}; +use crate::chat; use crate::constants::*; use crate::context::Context; -use crate::dc_chat::*; use crate::dc_configure::*; use crate::dc_imex::*; use crate::dc_jobthread::*; @@ -565,7 +565,7 @@ unsafe fn dc_job_do_DC_JOB_MARKSEEN_MSG_ON_IMAP(context: &Context, job: &mut dc_ dc_msg_unref(msg); } unsafe fn dc_send_mdn(context: &Context, msg_id: uint32_t) { - let mut mimefactory: dc_mimefactory_t = dc_mimefactory_t { + let mut mimefactory = dc_mimefactory_t { from_addr: ptr::null_mut(), from_displayname: ptr::null_mut(), selfstatus: ptr::null_mut(), @@ -575,7 +575,7 @@ unsafe fn dc_send_mdn(context: &Context, msg_id: uint32_t) { rfc724_mid: ptr::null_mut(), loaded: DC_MF_NOTHING_LOADED, msg: ptr::null_mut(), - chat: ptr::null_mut(), + chat: None, increation: 0, in_reply_to: ptr::null_mut(), references: ptr::null_mut(), @@ -587,7 +587,7 @@ unsafe fn dc_send_mdn(context: &Context, msg_id: uint32_t) { error: ptr::null_mut(), context, }; - dc_mimefactory_init(&mut mimefactory, context); + if !(0 == dc_mimefactory_load_mdn(&mut mimefactory, msg_id) || 0 == dc_mimefactory_render(&mut mimefactory)) { @@ -1014,7 +1014,7 @@ pub unsafe fn dc_job_send_msg(context: &Context, msg_id: uint32_t) -> libc::c_in rfc724_mid: 0 as *mut libc::c_char, loaded: DC_MF_NOTHING_LOADED, msg: 0 as *mut dc_msg_t, - chat: 0 as *mut Chat, + chat: None, increation: 0, in_reply_to: 0 as *mut libc::c_char, references: 0 as *mut libc::c_char, @@ -1026,7 +1026,7 @@ pub unsafe fn dc_job_send_msg(context: &Context, msg_id: uint32_t) -> libc::c_in error: 0 as *mut libc::c_char, context, }; - dc_mimefactory_init(&mut mimefactory, context); + /* load message data */ if 0 == dc_mimefactory_load_msg(&mut mimefactory, msg_id) || mimefactory.from_addr.is_null() { warn!( @@ -1035,7 +1035,7 @@ pub unsafe fn dc_job_send_msg(context: &Context, msg_id: uint32_t) -> libc::c_in ); } else { // no redo, no IMAP. moreover, as the data does not exist, there is no need in calling dc_set_msg_failed() - if msgtype_has_file((*mimefactory.msg).type_0) { + if chat::msgtype_has_file((*mimefactory.msg).type_0) { if let Some(pathNfilename) = (*mimefactory.msg).param.get(Param::File) { if ((*mimefactory.msg).type_0 == Viewtype::Image || (*mimefactory.msg).type_0 == Viewtype::Gif) @@ -1096,7 +1096,7 @@ pub unsafe fn dc_job_send_msg(context: &Context, msg_id: uint32_t) -> libc::c_in ); } if 0 != mimefactory.out_gossiped { - dc_set_gossiped_timestamp(context, (*mimefactory.msg).chat_id, time()); + chat::set_gossiped_timestamp(context, (*mimefactory.msg).chat_id, time()); } if 0 != mimefactory.out_last_added_location_id { dc_set_kml_sent_timestamp(context, (*mimefactory.msg).chat_id, time()); diff --git a/src/dc_location.rs b/src/dc_location.rs index 5c15c1ca5..86a23908e 100644 --- a/src/dc_location.rs +++ b/src/dc_location.rs @@ -1,12 +1,10 @@ -use std::ffi::CString; - use quick_xml; use quick_xml::events::{BytesEnd, BytesStart, BytesText}; +use crate::chat; use crate::constants::Event; use crate::constants::*; use crate::context::*; -use crate::dc_chat::*; use crate::dc_job::*; use crate::dc_msg::*; use crate::dc_tools::*; @@ -104,16 +102,11 @@ pub unsafe fn dc_send_locations_to_chat( (*msg).text = Some(context.stock_system_msg(StockMessage::MsgLocationEnabled, "", "", 0)); (*msg).param.set_int(Param::Cmd, 8); - dc_send_msg(context, chat_id, msg); + chat::send_msg(context, chat_id, msg).unwrap(); } else if 0 == seconds && is_sending_locations_before { - let stock_str = CString::new(context.stock_system_msg( - StockMessage::MsgLocationDisabled, - "", - "", - 0, - )) - .unwrap(); - dc_add_device_msg(context, chat_id, stock_str.as_ptr()); + let stock_str = + context.stock_system_msg(StockMessage::MsgLocationDisabled, "", "", 0); + chat::add_device_msg(context, chat_id, stock_str); } context.call_cb( Event::CHAT_MODIFIED, @@ -697,7 +690,8 @@ pub unsafe fn dc_job_do_DC_JOB_MAYBE_SEND_LOCATIONS(context: &Context, _job: *mu let mut msg = dc_msg_new(context, Viewtype::Text); (*msg).hidden = 1; (*msg).param.set_int(Param::Cmd, 9); - dc_send_msg(context, chat_id as u32, msg); + // TODO: handle cleanup on error + chat::send_msg(context, chat_id as u32, msg).unwrap(); dc_msg_unref(msg); } Ok(()) @@ -735,8 +729,8 @@ pub unsafe fn dc_job_do_DC_JOB_MAYBE_SEND_LOC_ENDED(context: &Context, job: &mut "UPDATE chats SET locations_send_begin=0, locations_send_until=0 WHERE id=?", params![chat_id as i32], ).is_ok() { - let stock_str = CString::new(context.stock_system_msg(StockMessage::MsgLocationDisabled, "", "", 0)).unwrap(); - dc_add_device_msg(context, chat_id, stock_str.as_ptr()); + let stock_str = context.stock_system_msg(StockMessage::MsgLocationDisabled, "", "", 0); + chat::add_device_msg(context, chat_id, stock_str); context.call_cb( Event::CHAT_MODIFIED, chat_id as usize, diff --git a/src/dc_lot.rs b/src/dc_lot.rs index 8829c9bbd..134a6bcf6 100644 --- a/src/dc_lot.rs +++ b/src/dc_lot.rs @@ -1,6 +1,7 @@ +use crate::chat::*; +use crate::constants::Chattype; use crate::contact::*; use crate::context::Context; -use crate::dc_chat::*; use crate::dc_msg::*; use crate::dc_tools::*; use crate::stock::StockMessage; @@ -123,7 +124,7 @@ pub unsafe fn dc_lot_get_timestamp(lot: *const dc_lot_t) -> i64 { pub unsafe fn dc_lot_fill( mut lot: *mut dc_lot_t, msg: *mut dc_msg_t, - chat: *const Chat, + chat: &Chat, contact: Option<&Contact>, context: &Context, ) { @@ -134,22 +135,19 @@ pub unsafe fn dc_lot_fill( (*lot).text1 = context.stock_str(StockMessage::Draft).strdup(); (*lot).text1_meaning = 1i32 } else if (*msg).from_id == 1i32 as libc::c_uint { - if 0 != dc_msg_is_info(msg) || 0 != dc_chat_is_self_talk(chat) { + if 0 != dc_msg_is_info(msg) || chat.is_self_talk() { (*lot).text1 = 0 as *mut libc::c_char; (*lot).text1_meaning = 0i32 } else { (*lot).text1 = context.stock_str(StockMessage::SelfMsg).strdup(); (*lot).text1_meaning = 3i32 } - } else if chat.is_null() { - (*lot).text1 = 0 as *mut libc::c_char; - (*lot).text1_meaning = 0i32 - } else if (*chat).type_0 == 120i32 || (*chat).type_0 == 130i32 { + } else if chat.typ == Chattype::Group || chat.typ == Chattype::VerifiedGroup { if 0 != dc_msg_is_info(msg) || contact.is_none() { (*lot).text1 = 0 as *mut libc::c_char; (*lot).text1_meaning = 0i32 } else { - if !chat.is_null() && (*chat).id == 1i32 as libc::c_uint { + if chat.id == 1 { if let Some(contact) = contact { (*lot).text1 = contact.get_display_name().strdup(); } else { diff --git a/src/dc_mimefactory.rs b/src/dc_mimefactory.rs index 17a8830ad..fe8dc2b0c 100644 --- a/src/dc_mimefactory.rs +++ b/src/dc_mimefactory.rs @@ -10,10 +10,10 @@ use mmime::mailmime_write_mem::*; use mmime::mmapstring::*; use mmime::other::*; +use crate::chat::{self, Chat}; use crate::constants::*; use crate::contact::*; use crate::context::{dc_get_version_str, Context}; -use crate::dc_chat::*; use crate::dc_e2ee::*; use crate::dc_location::*; use crate::dc_msg::*; @@ -24,8 +24,7 @@ use crate::stock::StockMessage; use crate::types::*; use crate::x::*; -#[derive(Copy, Clone)] -#[repr(C)] +#[derive(Clone)] #[allow(non_camel_case_types)] pub struct dc_mimefactory_t<'a> { pub from_addr: *mut libc::c_char, @@ -37,7 +36,7 @@ pub struct dc_mimefactory_t<'a> { pub rfc724_mid: *mut libc::c_char, pub loaded: dc_mimefactory_loaded_t, pub msg: *mut dc_msg_t<'a>, - pub chat: *mut Chat<'a>, + pub chat: Option>, pub increation: libc::c_int, pub in_reply_to: *mut libc::c_char, pub references: *mut libc::c_char, @@ -56,18 +55,6 @@ const DC_MF_MDN_LOADED: dc_mimefactory_loaded_t = 2; pub const DC_MF_MSG_LOADED: dc_mimefactory_loaded_t = 1; pub const DC_MF_NOTHING_LOADED: dc_mimefactory_loaded_t = 0; -pub unsafe fn dc_mimefactory_init<'a>(factory: *mut dc_mimefactory_t<'a>, context: &'a Context) { - if factory.is_null() { - return; - } - memset( - factory as *mut libc::c_void, - 0, - ::std::mem::size_of::(), - ); - (*factory).context = context; -} - pub unsafe fn dc_mimefactory_empty(mut factory: *mut dc_mimefactory_t) { if factory.is_null() { return; @@ -92,8 +79,7 @@ pub unsafe fn dc_mimefactory_empty(mut factory: *mut dc_mimefactory_t) { } dc_msg_unref((*factory).msg); (*factory).msg = 0 as *mut dc_msg_t; - dc_chat_unref((*factory).chat); - (*factory).chat = 0 as *mut Chat; + (*factory).chat = None; free((*factory).in_reply_to as *mut libc::c_void); (*factory).in_reply_to = 0 as *mut libc::c_char; free((*factory).references as *mut libc::c_void); @@ -124,131 +110,139 @@ pub unsafe fn dc_mimefactory_load_msg( (*factory).recipients_names = clist_new(); (*factory).recipients_addr = clist_new(); (*factory).msg = dc_msg_new_untyped(context); - (*factory).chat = dc_chat_new(context); - if dc_msg_load_from_db((*factory).msg, context, msg_id) - && dc_chat_load_from_db((*factory).chat, (*(*factory).msg).chat_id) - { - load_from(factory); - (*factory).req_mdn = 0; - if 0 != dc_chat_is_self_talk((*factory).chat) { - clist_insert_after( - (*factory).recipients_names, - (*(*factory).recipients_names).last, - dc_strdup_keep_null((*factory).from_displayname) as *mut libc::c_void, - ); - clist_insert_after( - (*factory).recipients_addr, - (*(*factory).recipients_addr).last, - dc_strdup((*factory).from_addr) as *mut libc::c_void, - ); - } else { - context - .sql - .query_map( - "SELECT c.authname, c.addr \ - FROM chats_contacts cc \ - LEFT JOIN contacts c ON cc.contact_id=c.id \ - WHERE cc.chat_id=? AND cc.contact_id>9;", - params![(*(*factory).msg).chat_id as i32], - |row| { - let authname: String = row.get(0)?; - let addr: String = row.get(1)?; - Ok((authname, addr)) - }, - |rows| { - for row in rows { - let (authname, addr) = row?; - let addr_c = addr.strdup(); - if clist_search_string_nocase((*factory).recipients_addr, addr_c) == 0 { - clist_insert_after( - (*factory).recipients_names, - (*(*factory).recipients_names).last, - if !authname.is_empty() { - authname.strdup() - } else { - std::ptr::null_mut() - } as *mut libc::c_void, - ); - clist_insert_after( - (*factory).recipients_addr, - (*(*factory).recipients_addr).last, - addr_c as *mut libc::c_void, - ); - } - } - Ok(()) - }, - ) - .unwrap(); - let command = (*(*factory).msg) - .param - .get_int(Param::Cmd) - .unwrap_or_default(); + if dc_msg_load_from_db((*factory).msg, context, msg_id) { + if let Ok(chat) = Chat::load_from_db(context, (*(*factory).msg).chat_id) { + (*factory).chat = Some(chat); - if command == 5 { - let email_to_remove = (*(*factory).msg).param.get(Param::Arg).unwrap_or_default(); - let email_to_remove_c = email_to_remove.strdup(); + let chat = (*factory).chat.as_ref().unwrap(); - let self_addr = context + load_from(factory); + (*factory).req_mdn = 0; + if chat.is_self_talk() { + clist_insert_after( + (*factory).recipients_names, + (*(*factory).recipients_names).last, + dc_strdup_keep_null((*factory).from_displayname) as *mut libc::c_void, + ); + clist_insert_after( + (*factory).recipients_addr, + (*(*factory).recipients_addr).last, + dc_strdup((*factory).from_addr) as *mut libc::c_void, + ); + } else { + context .sql - .get_config(context, "configured_addr") + .query_map( + "SELECT c.authname, c.addr \ + FROM chats_contacts cc \ + LEFT JOIN contacts c ON cc.contact_id=c.id \ + WHERE cc.chat_id=? AND cc.contact_id>9;", + params![(*(*factory).msg).chat_id as i32], + |row| { + let authname: String = row.get(0)?; + let addr: String = row.get(1)?; + Ok((authname, addr)) + }, + |rows| { + for row in rows { + let (authname, addr) = row?; + let addr_c = addr.strdup(); + if clist_search_string_nocase((*factory).recipients_addr, addr_c) + == 0 + { + clist_insert_after( + (*factory).recipients_names, + (*(*factory).recipients_names).last, + if !authname.is_empty() { + authname.strdup() + } else { + std::ptr::null_mut() + } + as *mut libc::c_void, + ); + clist_insert_after( + (*factory).recipients_addr, + (*(*factory).recipients_addr).last, + addr_c as *mut libc::c_void, + ); + } + } + Ok(()) + }, + ) + .unwrap(); + + let command = (*(*factory).msg) + .param + .get_int(Param::Cmd) .unwrap_or_default(); - if !email_to_remove.is_empty() && email_to_remove != self_addr { - if clist_search_string_nocase((*factory).recipients_addr, email_to_remove_c) - == 0 - { - clist_insert_after( - (*factory).recipients_names, - (*(*factory).recipients_names).last, - 0 as *mut libc::c_void, - ); - clist_insert_after( - (*factory).recipients_addr, - (*(*factory).recipients_addr).last, - email_to_remove_c as *mut libc::c_void, - ); + if command == 5 { + let email_to_remove = + (*(*factory).msg).param.get(Param::Arg).unwrap_or_default(); + let email_to_remove_c = email_to_remove.strdup(); + + let self_addr = context + .sql + .get_config(context, "configured_addr") + .unwrap_or_default(); + + if !email_to_remove.is_empty() && email_to_remove != self_addr { + if clist_search_string_nocase((*factory).recipients_addr, email_to_remove_c) + == 0 + { + clist_insert_after( + (*factory).recipients_names, + (*(*factory).recipients_names).last, + 0 as *mut libc::c_void, + ); + clist_insert_after( + (*factory).recipients_addr, + (*(*factory).recipients_addr).last, + email_to_remove_c as *mut libc::c_void, + ); + } } } + if command != 6 + && command != 7 + && 0 != context + .sql + .get_config_int(context, "mdns_enabled") + .unwrap_or_else(|| 1) + { + (*factory).req_mdn = 1 + } } - if command != 6 - && command != 7 - && 0 != context - .sql - .get_config_int(context, "mdns_enabled") - .unwrap_or_else(|| 1) - { - (*factory).req_mdn = 1 - } - } - let row = context.sql.query_row( - "SELECT mime_in_reply_to, mime_references FROM msgs WHERE id=?", - params![(*(*factory).msg).id as i32], - |row| { - let in_reply_to: String = row.get(0)?; - let references: String = row.get(1)?; + let row = context.sql.query_row( + "SELECT mime_in_reply_to, mime_references FROM msgs WHERE id=?", + params![(*(*factory).msg).id as i32], + |row| { + let in_reply_to: String = row.get(0)?; + let references: String = row.get(1)?; - Ok((in_reply_to, references)) - }, - ); - match row { - Ok((in_reply_to, references)) => { - (*factory).in_reply_to = in_reply_to.strdup(); - (*factory).references = references.strdup(); + Ok((in_reply_to, references)) + }, + ); + match row { + Ok((in_reply_to, references)) => { + (*factory).in_reply_to = in_reply_to.strdup(); + (*factory).references = references.strdup(); + } + Err(err) => { + error!( + context, + 0, "mimefactory: failed to load mime_in_reply_to: {:?}", err + ); + } } - Err(err) => { - error!( - context, - 0, "mimefactory: failed to load mime_in_reply_to: {:?}", err - ); - } - } - success = 1; - (*factory).loaded = DC_MF_MSG_LOADED; - (*factory).timestamp = (*(*factory).msg).timestamp_sort; - (*factory).rfc724_mid = dc_strdup((*(*factory).msg).rfc724_mid) + success = 1; + (*factory).loaded = DC_MF_MSG_LOADED; + (*factory).timestamp = (*(*factory).msg).timestamp_sort; + (*factory).rfc724_mid = dc_strdup((*(*factory).msg).rfc724_mid) + } } if 0 != success { (*factory).increation = dc_msg_is_increation((*factory).msg) @@ -514,11 +508,11 @@ pub unsafe fn dc_mimefactory_render(mut factory: *mut dc_mimefactory_t) -> libc: if (*factory).loaded as libc::c_uint == DC_MF_MSG_LOADED as libc::c_int as libc::c_uint { /* Render a normal message *********************************************************************/ - let chat: *mut Chat = (*factory).chat; - let msg: *mut dc_msg_t = (*factory).msg; + let chat = (*factory).chat.as_ref().unwrap(); + let msg = (*factory).msg; let mut meta_part: *mut mailmime = 0 as *mut mailmime; let mut placeholdertext: *mut libc::c_char = 0 as *mut libc::c_char; - if (*chat).type_0 == DC_CHAT_TYPE_VERIFIED_GROUP as libc::c_int { + if chat.typ == Chattype::VerifiedGroup { mailimf_fields_add( imf_fields, mailimf_field_new_custom( @@ -541,29 +535,28 @@ pub unsafe fn dc_mimefactory_render(mut factory: *mut dc_mimefactory_t) -> libc: .unwrap_or_default() } } - if (*chat).gossiped_timestamp == 0 - || ((*chat).gossiped_timestamp + (2 * 24 * 60 * 60)) < time() + if chat.gossiped_timestamp == 0 + || (chat.gossiped_timestamp + (2 * 24 * 60 * 60)) < time() { do_gossip = 1 } /* build header etc. */ let command = (*msg).param.get_int(Param::Cmd).unwrap_or_default(); - if (*chat).type_0 == DC_CHAT_TYPE_GROUP as libc::c_int - || (*chat).type_0 == DC_CHAT_TYPE_VERIFIED_GROUP as libc::c_int - { + if chat.typ == Chattype::Group || chat.typ == Chattype::VerifiedGroup { mailimf_fields_add( imf_fields, mailimf_field_new_custom( strdup(b"Chat-Group-ID\x00" as *const u8 as *const libc::c_char), - dc_strdup((*chat).grpid), + chat.grpid.strdup(), ), ); + let name = CString::yolo(chat.name.as_bytes()); mailimf_fields_add( imf_fields, mailimf_field_new_custom( strdup(b"Chat-Group-Name\x00" as *const u8 as *const libc::c_char), - dc_encode_header_words((*chat).name), + dc_encode_header_words(name.as_ptr()), ), ); if command == 5 { @@ -594,7 +587,7 @@ pub unsafe fn dc_mimefactory_render(mut factory: *mut dc_mimefactory_t) -> libc: email_to_add, ), ); - grpimage = (*chat).param.get(Param::ProfileImage); + grpimage = chat.param.get(Param::ProfileImage); } if 0 != (*msg).param.get_int(Param::Arg2).unwrap_or_default() & 0x1 { info!( @@ -838,7 +831,7 @@ pub unsafe fn dc_mimefactory_render(mut factory: *mut dc_mimefactory_t) -> libc: free(placeholdertext as *mut libc::c_void); /* add attachment part */ - if msgtype_has_file((*msg).type_0) { + if chat::msgtype_has_file((*msg).type_0) { if !is_file_size_okay(msg) { let error: *mut libc::c_char = dc_mprintf( b"Message exceeds the recommended %i MB.\x00" as *const u8 @@ -1000,7 +993,7 @@ pub unsafe fn dc_mimefactory_render(mut factory: *mut dc_mimefactory_t) -> libc: e.as_ptr(), ); } else { - subject_str = get_subject((*factory).chat, (*factory).msg, afwd_email) + subject_str = get_subject((*factory).chat.as_ref(), (*factory).msg, afwd_email) } subject = mailimf_subject_new(dc_encode_header_words(subject_str)); mailimf_fields_add( @@ -1054,6 +1047,7 @@ pub unsafe fn dc_mimefactory_render(mut factory: *mut dc_mimefactory_t) -> libc: success = 1 } } + if !message.is_null() { mailmime_free(message); } @@ -1066,11 +1060,16 @@ pub unsafe fn dc_mimefactory_render(mut factory: *mut dc_mimefactory_t) -> libc: } unsafe fn get_subject( - chat: *const Chat, + chat: Option<&Chat>, msg: *mut dc_msg_t, afwd_email: libc::c_int, ) -> *mut libc::c_char { - let context = (*chat).context; + if chat.is_none() { + return std::ptr::null_mut(); + } + + let chat = chat.unwrap(); + let context = chat.context; let ret: *mut libc::c_char; let raw_subject = { @@ -1091,15 +1090,14 @@ unsafe fn get_subject( }; if (*msg).param.get_int(Param::Cmd).unwrap_or_default() == 6 { ret = context.stock_str(StockMessage::AcSetupMsgSubject).strdup() - } else if (*chat).type_0 == DC_CHAT_TYPE_GROUP as libc::c_int - || (*chat).type_0 == DC_CHAT_TYPE_VERIFIED_GROUP as libc::c_int - { - ret = dc_mprintf( - b"Chat: %s: %s%s\x00" as *const u8 as *const libc::c_char, - (*chat).name, - fwd, - raw_subject, + } else if chat.typ == Chattype::Group || chat.typ == Chattype::VerifiedGroup { + ret = format!( + "Chat: {}: {}{}", + chat.name, + to_string(fwd), + to_string(raw_subject), ) + .strdup() } else { ret = dc_mprintf( b"Chat: %s%s\x00" as *const u8 as *const libc::c_char, @@ -1150,22 +1148,20 @@ unsafe fn build_body_file( let mime_fields: *mut mailmime_fields; let mut mime_sub: *mut mailmime = 0 as *mut mailmime; let content: *mut mailmime_content; - let pathNfilename = (*msg) - .param - .get(Param::File) - .map(|s| s.strdup()) - .unwrap_or_else(|| std::ptr::null_mut()); + let path_filename = (*msg).param.get(Param::File); + let mut mimetype = (*msg) .param .get(Param::MimeType) .map(|s| s.strdup()) .unwrap_or_else(|| std::ptr::null_mut()); - let suffix = dc_get_filesuffix_lc(pathNfilename); let mut filename_to_send = 0 as *mut libc::c_char; let mut filename_encoded = 0 as *mut libc::c_char; - if !pathNfilename.is_null() { + if let Some(ref path_filename) = path_filename { + let suffix = dc_get_filesuffix_lc(path_filename); + if (*msg).type_0 == Viewtype::Voice { let ts = chrono::Utc.timestamp((*msg).timestamp_sort as i64, 0); @@ -1179,7 +1175,7 @@ unsafe fn build_body_file( .to_string(); filename_to_send = res.strdup(); } else if (*msg).type_0 == Viewtype::Audio { - filename_to_send = dc_get_filename(pathNfilename) + filename_to_send = dc_get_filename(path_filename) } else if (*msg).type_0 == Viewtype::Image || (*msg).type_0 == Viewtype::Gif { if base_name.is_null() { base_name = b"image\x00" as *const u8 as *const libc::c_char @@ -1203,7 +1199,7 @@ unsafe fn build_body_file( }, ) } else { - filename_to_send = dc_get_filename(pathNfilename) + filename_to_send = dc_get_filename(path_filename) } if mimetype.is_null() { if suffix.is_null() { @@ -1296,17 +1292,16 @@ unsafe fn build_body_file( ) as *mut libc::c_void, ); mime_sub = mailmime_new_empty(content, mime_fields); - mailmime_set_body_file(mime_sub, dc_get_abs_path((*msg).context, pathNfilename)); + mailmime_set_body_file(mime_sub, dc_get_abs_path((*msg).context, path_filename)); if !ret_file_name_as_sent.is_null() { *ret_file_name_as_sent = dc_strdup(filename_to_send) } } } - free(pathNfilename as *mut libc::c_void); + free(mimetype as *mut libc::c_void); free(filename_to_send as *mut libc::c_void); free(filename_encoded as *mut libc::c_void); - free(suffix as *mut libc::c_void); mime_sub } diff --git a/src/dc_msg.rs b/src/dc_msg.rs index 8e8e58699..20713f170 100644 --- a/src/dc_msg.rs +++ b/src/dc_msg.rs @@ -1,10 +1,10 @@ use std::ffi::CString; use std::path::Path; +use crate::chat::{self, Chat}; use crate::constants::*; use crate::contact::*; use crate::context::*; -use crate::dc_chat::*; use crate::dc_job::*; use crate::dc_lot::dc_lot_t; use crate::dc_lot::*; @@ -309,8 +309,7 @@ pub unsafe fn dc_msg_get_file(msg: *const dc_msg_t) -> *mut libc::c_char { if !msg.is_null() { if let Some(file_rel) = (*msg).param.get(Param::File) { - let file_rel_c = CString::yolo(file_rel); - file_abs = dc_get_abs_path((*msg).context, file_rel_c.as_ptr()); + file_abs = dc_get_abs_path((*msg).context, file_rel); } } if !file_abs.is_null() { @@ -681,8 +680,7 @@ pub unsafe fn dc_msg_get_filename(msg: *const dc_msg_t) -> *mut libc::c_char { if !msg.is_null() { if let Some(file) = (*msg).param.get(Param::File) { - let file_c = CString::yolo(file); - ret = dc_get_filename(file_c.as_ptr()); + ret = dc_get_filename(file); } } if !ret.is_null() { @@ -745,35 +743,35 @@ pub unsafe fn dc_msg_get_showpadlock(msg: *const dc_msg_t) -> libc::c_int { pub unsafe fn dc_msg_get_summary<'a>( msg: *mut dc_msg_t<'a>, - mut chat: *const Chat<'a>, + chat: Option<&Chat<'a>>, ) -> *mut dc_lot_t { - let mut ok_to_continue = true; - let ret: *mut dc_lot_t = dc_lot_new(); - let mut chat_to_delete: *mut Chat = 0 as *mut Chat; + let ret = dc_lot_new(); - if !msg.is_null() { - if chat.is_null() { - chat_to_delete = dc_get_chat((*msg).context, (*msg).chat_id); - if chat_to_delete.is_null() { - ok_to_continue = false; - } else { - chat = chat_to_delete; - } - } - if ok_to_continue { - let contact = if (*msg).from_id != DC_CONTACT_ID_SELF as libc::c_uint - && ((*chat).type_0 == 120 || (*chat).type_0 == 130) - { - Contact::get_by_id((*chat).context, (*msg).from_id).ok() - } else { - None - }; - - dc_lot_fill(ret, msg, chat, contact.as_ref(), (*msg).context); - } + if msg.is_null() { + return ret; } - dc_chat_unref(chat_to_delete); + let chat_loaded: Chat; + let chat = if let Some(chat) = chat { + chat + } else { + if let Ok(chat) = Chat::load_from_db((*msg).context, (*msg).chat_id) { + chat_loaded = chat; + &chat_loaded + } else { + return ret; + } + }; + + let contact = if (*msg).from_id != DC_CONTACT_ID_SELF as libc::c_uint + && ((*chat).typ == Chattype::Group || (*chat).typ == Chattype::VerifiedGroup) + { + Contact::get_by_id((*chat).context, (*msg).from_id).ok() + } else { + None + }; + + dc_lot_fill(ret, msg, chat, contact.as_ref(), (*msg).context); ret } @@ -921,7 +919,7 @@ pub unsafe fn dc_msg_is_increation(msg: *const dc_msg_t) -> libc::c_int { return 0; } - if msgtype_has_file((*msg).type_0) && (*msg).state == DC_STATE_OUT_PREPARING { + if chat::msgtype_has_file((*msg).type_0) && (*msg).state == DC_STATE_OUT_PREPARING { 1 } else { 0 @@ -1252,7 +1250,7 @@ pub unsafe fn dc_mdn_from_ext( (S=Sender, R=Recipient) */ // for rounding, SELF is already included! - let soll_cnt = (dc_get_chat_contact_cnt(context, *ret_chat_id) + 1) / 2; + let soll_cnt = (chat::get_chat_contact_cnt(context, *ret_chat_id) + 1) / 2; if ist_cnt >= soll_cnt { dc_update_msg_state(context, *ret_msg_id, DC_STATE_OUT_MDN_RCVD); read_by_all = 1; @@ -1392,14 +1390,12 @@ mod tests { let res = ctx.set_config(Config::ConfiguredAddr, Some("self@example.com")); assert!(res.is_ok()); - let chat = dc_create_chat_by_contact_id(ctx, contact); - assert!(chat != 0); + let chat = chat::create_by_contact_id(ctx, contact).unwrap(); let msg = dc_msg_new(ctx, Viewtype::Text); assert!(!msg.is_null()); - let msg_id = dc_prepare_msg(ctx, chat, msg); - assert!(msg_id != 0); + let msg_id = chat::prepare_msg(ctx, chat, msg).unwrap(); let msg2 = dc_get_msg(ctx, msg_id); assert!(!msg2.is_null()); diff --git a/src/dc_qr.rs b/src/dc_qr.rs index cb80eccdd..82951c021 100644 --- a/src/dc_qr.rs +++ b/src/dc_qr.rs @@ -1,8 +1,9 @@ use percent_encoding::percent_decode_str; +use crate::chat; +use crate::constants::Blocked; use crate::contact::*; use crate::context::Context; -use crate::dc_chat::*; use crate::dc_lot::*; use crate::dc_strencode::*; use crate::dc_tools::*; @@ -34,7 +35,7 @@ pub unsafe fn dc_check_qr(context: &Context, qr: *const libc::c_char) -> *mut dc let mut auth: *mut libc::c_char = 0 as *mut libc::c_char; let mut qr_parsed: *mut dc_lot_t = dc_lot_new(); let mut chat_id: uint32_t = 0i32 as uint32_t; - let mut device_msg: *mut libc::c_char = 0 as *mut libc::c_char; + let mut device_msg = "".to_string(); let mut grpid: *mut libc::c_char = 0 as *mut libc::c_char; let mut grpname: *mut libc::c_char = 0 as *mut libc::c_char; (*qr_parsed).state = 0i32; @@ -230,20 +231,17 @@ pub unsafe fn dc_check_qr(context: &Context, qr: *const libc::c_char) -> *mut dc Contact::add_or_lookup(context, "", addr, Origin::UnhandledQrScan) .map(|(id, _)| id) .unwrap_or_default(); - dc_create_or_lookup_nchat_by_contact_id( + let (id, _) = chat::create_or_lookup_by_contact_id( context, (*qr_parsed).id, - 2i32, - &mut chat_id, - 0 as *mut libc::c_int, - ); - device_msg = dc_mprintf( - b"%s verified.\x00" as *const u8 as *const libc::c_char, - peerstate.addr, + Blocked::Deaddrop, ) + .unwrap_or_default(); + chat_id = id; + device_msg = format!("{} verified.", peerstate.addr.unwrap_or_default()); } else { (*qr_parsed).text1 = dc_format_fingerprint_c(fingerprint); - (*qr_parsed).state = 230i32 + (*qr_parsed).state = 230i32; } } else { if !grpid.is_null() && !grpname.is_null() { @@ -286,8 +284,8 @@ pub unsafe fn dc_check_qr(context: &Context, qr: *const libc::c_char) -> *mut dc (*qr_parsed).state = 330i32; (*qr_parsed).text1 = dc_strdup(qr) } - if !device_msg.is_null() { - dc_add_device_msg(context, chat_id, device_msg); + if !device_msg.is_empty() { + chat::add_device_msg(context, chat_id, device_msg); } } } @@ -297,7 +295,6 @@ pub unsafe fn dc_check_qr(context: &Context, qr: *const libc::c_char) -> *mut dc free(name as *mut libc::c_void); free(invitenumber as *mut libc::c_void); free(auth as *mut libc::c_void); - free(device_msg as *mut libc::c_void); free(grpname as *mut libc::c_void); free(grpid as *mut libc::c_void); diff --git a/src/dc_receive_imf.rs b/src/dc_receive_imf.rs index ac1b4c50b..bce036626 100644 --- a/src/dc_receive_imf.rs +++ b/src/dc_receive_imf.rs @@ -1,5 +1,6 @@ -use itertools::join; +use std::ptr; +use itertools::join; use mmime::mailimf::*; use mmime::mailimf_types::*; use mmime::mailmime::*; @@ -8,12 +9,11 @@ use mmime::mailmime_types::*; use mmime::mmapstring::*; use mmime::other::*; use sha2::{Digest, Sha256}; -use std::ptr; +use crate::chat::{self, Chat}; use crate::constants::*; use crate::contact::*; use crate::context::Context; -use crate::dc_chat::*; use crate::dc_job::*; use crate::dc_location::*; use crate::dc_mimeparser::*; @@ -284,7 +284,7 @@ unsafe fn add_parts( ) -> Result<()> { let mut state: libc::c_int; let mut msgrmsg: libc::c_int; - let mut chat_id_blocked = 0; + let mut chat_id_blocked = Blocked::Not; let mut sort_timestamp = 0; let mut rcvd_timestamp = 0; let mut mime_in_reply_to = std::ptr::null_mut(); @@ -408,15 +408,9 @@ unsafe fn add_parts( state = DC_STATE_IN_SEEN; } } - let mut test_normal_chat_id = 0; - let mut test_normal_chat_id_blocked = 0; - dc_lookup_real_nchat_by_contact_id( - context, - *from_id, - &mut test_normal_chat_id, - &mut test_normal_chat_id_blocked, - ); + let (test_normal_chat_id, test_normal_chat_id_blocked) = + chat::lookup_by_contact_id(context, *from_id).unwrap_or_default(); // 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 @@ -425,12 +419,12 @@ unsafe fn add_parts( // (groups appear automatically only if the _sender_ is known, see core issue #54) let create_blocked = if 0 != test_normal_chat_id - && test_normal_chat_id_blocked == DC_CHAT_NOT_BLOCKED + && test_normal_chat_id_blocked == Blocked::Not || incoming_origin.is_start_new_chat() { - DC_CHAT_NOT_BLOCKED + Blocked::Not } else { - DC_CHAT_DEADDROP_BLOCKED + Blocked::Deaddrop }; create_or_lookup_group( @@ -443,9 +437,9 @@ unsafe fn add_parts( chat_id, &mut chat_id_blocked, ); - if 0 != *chat_id && 0 != chat_id_blocked && 0 == create_blocked { - dc_unblock_chat(context, *chat_id); - chat_id_blocked = 0; + if 0 != *chat_id && Blocked::Not != chat_id_blocked && create_blocked == Blocked::Not { + chat::unblock(context, *chat_id); + chat_id_blocked = Blocked::Not; } } @@ -463,27 +457,25 @@ unsafe fn add_parts( if *chat_id == 0 { // try to create a normal chat let create_blocked = if incoming_origin.is_start_new_chat() || *from_id == *to_id { - DC_CHAT_NOT_BLOCKED + Blocked::Not } else { - DC_CHAT_DEADDROP_BLOCKED + Blocked::Deaddrop }; if 0 != test_normal_chat_id { *chat_id = test_normal_chat_id; - chat_id_blocked = test_normal_chat_id_blocked + chat_id_blocked = test_normal_chat_id_blocked; } else if 0 != allow_creation { - dc_create_or_lookup_nchat_by_contact_id( - context, - *from_id, - create_blocked, - chat_id, - &mut chat_id_blocked, - ); + let (id, bl) = + chat::create_or_lookup_by_contact_id(context, *from_id, create_blocked) + .unwrap_or_default(); + *chat_id = id; + chat_id_blocked = bl; } - if 0 != *chat_id && 0 != chat_id_blocked { - if 0 == create_blocked { - dc_unblock_chat(context, *chat_id); - chat_id_blocked = 0; + if 0 != *chat_id && Blocked::Not != chat_id_blocked { + if Blocked::Not == create_blocked { + chat::unblock(context, *chat_id); + chat_id_blocked = Blocked::Not; } else if 0 != dc_is_reply_to_known_message(context, mime_parser) { // we do not want any chat to be created implicitly. Because of the origin-scale-up, // the contact requests will pop up and this should be just fine. @@ -506,7 +498,7 @@ unsafe fn add_parts( // if the chat_id is blocked, // for unknown senders and non-delta messages set the state to NOTICED // to not result in a contact request (this would require the state FRESH) - if 0 != chat_id_blocked && state == DC_STATE_IN_FRESH { + if Blocked::Not != chat_id_blocked && state == DC_STATE_IN_FRESH { if !incoming_origin.is_verified() && msgrmsg == 0 { state = DC_STATE_IN_NOTICED; } @@ -525,33 +517,35 @@ unsafe fn add_parts( context, &mut mime_parser, allow_creation, - 0, + Blocked::Not, *from_id, to_ids, chat_id, &mut chat_id_blocked, ); - if 0 != *chat_id && 0 != chat_id_blocked { - dc_unblock_chat(context, *chat_id); - chat_id_blocked = 0 + if 0 != *chat_id && Blocked::Not != chat_id_blocked { + chat::unblock(context, *chat_id); + chat_id_blocked = Blocked::Not; } } if *chat_id == 0 && 0 != allow_creation { let create_blocked = if 0 != msgrmsg && !Contact::is_blocked_load(context, *to_id) { - DC_CHAT_NOT_BLOCKED + Blocked::Not } else { - DC_CHAT_DEADDROP_BLOCKED + Blocked::Deaddrop }; - dc_create_or_lookup_nchat_by_contact_id( - context, - *to_id, - create_blocked, - chat_id, - &mut chat_id_blocked, - ); - if 0 != *chat_id && 0 != chat_id_blocked && 0 == create_blocked { - dc_unblock_chat(context, *chat_id); - chat_id_blocked = 0 + let (id, bl) = + chat::create_or_lookup_by_contact_id(context, *to_id, create_blocked) + .unwrap_or_default(); + *chat_id = id; + chat_id_blocked = bl; + + if 0 != *chat_id + && Blocked::Not != chat_id_blocked + && Blocked::Not == create_blocked + { + chat::unblock(context, *chat_id); + chat_id_blocked = Blocked::Not; } } } @@ -559,16 +553,14 @@ unsafe fn add_parts( if to_ids.is_empty() && 0 != to_self { // from_id==to_id==DC_CONTACT_ID_SELF - this is a self-sent messages, // maybe an Autocrypt Setup Messag - dc_create_or_lookup_nchat_by_contact_id( - context, - 1, - 0, - chat_id, - &mut chat_id_blocked, - ); - if 0 != *chat_id && 0 != chat_id_blocked { - dc_unblock_chat(context, *chat_id); - chat_id_blocked = 0; + let (id, bl) = chat::create_or_lookup_by_contact_id(context, 1, Blocked::Not) + .unwrap_or_default(); + *chat_id = id; + chat_id_blocked = bl; + + if 0 != *chat_id && Blocked::Not != chat_id_blocked { + chat::unblock(context, *chat_id); + chat_id_blocked = Blocked::Not; } } } @@ -590,7 +582,7 @@ unsafe fn add_parts( ); // unarchive chat - dc_unarchive_chat(context, *chat_id); + chat::unarchive(context, *chat_id).unwrap(); // if the mime-headers should be saved, find out its size // (the mime-header ends with an empty line) @@ -743,7 +735,7 @@ unsafe fn add_parts( } else if 0 != incoming && state == DC_STATE_IN_FRESH { if 0 != from_id_blocked { *create_event_to_send = None; - } else if 0 != chat_id_blocked { + } else if Blocked::Not != chat_id_blocked { *create_event_to_send = Some(Event::MSGS_CHANGED); } else { *create_event_to_send = Some(Event::INCOMING_MSG); @@ -1043,17 +1035,17 @@ unsafe fn create_or_lookup_group( context: &Context, mime_parser: &mut dc_mimeparser_t, allow_creation: libc::c_int, - create_blocked: libc::c_int, + create_blocked: Blocked, from_id: u32, to_ids: &mut Vec, ret_chat_id: *mut uint32_t, - ret_chat_id_blocked: *mut libc::c_int, + ret_chat_id_blocked: &mut Blocked, ) { - let group_explicitly_left: libc::c_int; + let group_explicitly_left: bool; let mut chat_id = 0; - let mut chat_id_blocked = 0; + let mut chat_id_blocked = Blocked::Not; let mut chat_id_verified = 0; - let mut grpid = std::ptr::null_mut(); + let mut grpid = "".to_string(); let mut grpname = std::ptr::null_mut(); let to_ids_cnt = to_ids.len(); let mut recreate_member_list = 0; @@ -1067,23 +1059,23 @@ unsafe fn create_or_lookup_group( let mut better_msg: String = From::from(""); let mut failure_reason = std::ptr::null_mut(); - let cleanup = |grpid: *mut libc::c_char, - grpname: *mut libc::c_char, + let cleanup = |grpname: *mut libc::c_char, failure_reason: *mut libc::c_char, ret_chat_id: *mut uint32_t, - ret_chat_id_blocked: *mut libc::c_int, + ret_chat_id_blocked: &mut Blocked, chat_id: u32, - chat_id_blocked: i32| { - free(grpid.cast()); + chat_id_blocked: Blocked| { free(grpname.cast()); free(failure_reason.cast()); if !ret_chat_id.is_null() { *ret_chat_id = chat_id; } - if !ret_chat_id_blocked.is_null() { - *ret_chat_id_blocked = if 0 != chat_id { chat_id_blocked } else { 0 }; - } + *ret_chat_id_blocked = if 0 != chat_id { + chat_id_blocked + } else { + Blocked::Not + }; }; if mime_parser.is_system_message == DC_CMD_LOCATION_STREAMING_ENABLED { @@ -1095,40 +1087,44 @@ unsafe fn create_or_lookup_group( // search the grpid in the header let optional_field = dc_mimeparser_lookup_optional_field(mime_parser, "Chat-Group-ID"); if !optional_field.is_null() { - grpid = dc_strdup((*optional_field).fld_value) + grpid = to_string((*optional_field).fld_value) } - if grpid.is_null() { + if grpid.is_empty() { if let Some(field) = lookup_field(mime_parser, "Message-ID", MAILIMF_FIELD_MESSAGE_ID) { let fld_message_id = (*field).fld_data.fld_message_id; if !fld_message_id.is_null() { if let Some(extracted_grpid) = dc_extract_grpid_from_rfc724_mid(as_str((*fld_message_id).mid_value)) { - grpid = extracted_grpid.strdup(); + grpid = extracted_grpid.to_string(); } else { - grpid = std::ptr::null_mut(); + grpid = "".to_string(); } } } - if grpid.is_null() { + if grpid.is_empty() { if let Some(field) = lookup_field(mime_parser, "In-Reply-To", MAILIMF_FIELD_IN_REPLY_TO) { let fld_in_reply_to = (*field).fld_data.fld_in_reply_to; if !fld_in_reply_to.is_null() { - grpid = dc_extract_grpid_from_rfc724_mid_list((*fld_in_reply_to).mid_list) + grpid = to_string(dc_extract_grpid_from_rfc724_mid_list( + (*fld_in_reply_to).mid_list, + )); } } - if grpid.is_null() { + if grpid.is_empty() { if let Some(field) = lookup_field(mime_parser, "References", MAILIMF_FIELD_REFERENCES) { let fld_references = (*field).fld_data.fld_references; if !fld_references.is_null() { - grpid = dc_extract_grpid_from_rfc724_mid_list((*fld_references).mid_list) + grpid = to_string(dc_extract_grpid_from_rfc724_mid_list( + (*fld_references).mid_list, + )); } } - if grpid.is_null() { + if grpid.is_empty() { create_or_lookup_adhoc_group( context, mime_parser, @@ -1140,7 +1136,6 @@ unsafe fn create_or_lookup_group( &mut chat_id_blocked, ); cleanup( - grpid, grpname, failure_reason, ret_chat_id, @@ -1231,7 +1226,12 @@ unsafe fn create_or_lookup_group( set_better_msg(mime_parser, &better_msg); // check, if we have a chat with this group ID - chat_id = dc_get_chat_id_by_grpid(context, grpid, &mut chat_id_blocked, &mut chat_id_verified); + chat_id = chat::get_chat_id_by_grpid( + context, + &grpid, + Some(&mut chat_id_blocked), + &mut chat_id_verified, + ); if chat_id != 0 { if 0 != chat_id_verified && 0 == check_verified_properties( @@ -1248,12 +1248,12 @@ unsafe fn create_or_lookup_group( // check if the sender is a member of the existing group - // if not, we'll recreate the group list - if chat_id != 0 && 0 == dc_is_contact_in_chat(context, chat_id, from_id as u32) { + if chat_id != 0 && 0 == chat::is_contact_in_chat(context, chat_id, from_id as u32) { recreate_member_list = 1; } // check if the group does not exist but should be created - group_explicitly_left = dc_is_group_explicitly_left(context, grpid); + group_explicitly_left = chat::is_group_explicitly_left(context, &grpid).unwrap_or_default(); let self_addr = context .sql @@ -1261,17 +1261,17 @@ unsafe fn create_or_lookup_group( .unwrap_or_default(); if chat_id == 0 && 0 == dc_mimeparser_is_mailinglist_message(mime_parser) - && !grpid.is_null() + && !grpid.is_empty() && !grpname.is_null() // otherwise, a pending "quit" message may pop up && X_MrRemoveFromGrp.is_null() // re-create explicitly left groups only if ourself is re-added - && (0 == group_explicitly_left + && (!group_explicitly_left || !X_MrAddToGrp.is_null() && addr_cmp(&self_addr, as_str(X_MrAddToGrp))) { - let mut create_verified: libc::c_int = 0; + let mut create_verified = VerifiedStatus::Unverified; if !dc_mimeparser_lookup_field(mime_parser, "Chat-Verified").is_null() { - create_verified = 1; + create_verified = VerifiedStatus::Verified; if 0 == check_verified_properties( context, mime_parser, @@ -1284,7 +1284,6 @@ unsafe fn create_or_lookup_group( } if 0 == allow_creation { cleanup( - grpid, grpname, failure_reason, ret_chat_id, @@ -1294,7 +1293,7 @@ unsafe fn create_or_lookup_group( ); return; } - chat_id = create_group_record(context, grpid, grpname, create_blocked, create_verified); + chat_id = create_group_record(context, &grpid, grpname, create_blocked, create_verified); chat_id_blocked = create_blocked; recreate_member_list = 1; } @@ -1302,7 +1301,7 @@ unsafe fn create_or_lookup_group( // again, check chat_id if chat_id <= DC_CHAT_ID_LAST_SPECIAL as u32 { chat_id = 0; - if 0 != group_explicitly_left { + if group_explicitly_left { chat_id = DC_CHAT_ID_TRASH as u32; } else { create_or_lookup_adhoc_group( @@ -1317,7 +1316,6 @@ unsafe fn create_or_lookup_group( ); } cleanup( - grpid, grpname, failure_reason, ret_chat_id, @@ -1365,7 +1363,6 @@ unsafe fn create_or_lookup_group( } } if 0 != ok { - let chat = dc_chat_new(context); info!( context, 0, @@ -1376,16 +1373,17 @@ unsafe fn create_or_lookup_group( to_string(grpimage) }, ); - dc_chat_load_from_db(chat, chat_id); - if grpimage.is_null() { - (*chat).param.remove(Param::ProfileImage); - } else { - (*chat).param.set(Param::ProfileImage, as_str(grpimage)); + if let Ok(mut chat) = Chat::load_from_db(context, chat_id) { + if grpimage.is_null() { + chat.param.remove(Param::ProfileImage); + } else { + chat.param.set(Param::ProfileImage, as_str(grpimage)); + } + chat.update_param().unwrap(); + send_EVENT_CHAT_MODIFIED = 1; } - dc_chat_update_param(chat); - dc_chat_unref(chat); + free(grpimage as *mut libc::c_void); - send_EVENT_CHAT_MODIFIED = 1 } } @@ -1409,14 +1407,14 @@ unsafe fn create_or_lookup_group( ) .ok(); if skip.is_null() || !addr_cmp(&self_addr, as_str(skip)) { - dc_add_to_chat_contacts_table(context, chat_id, DC_CONTACT_ID_SELF as u32); + chat::add_to_chat_contacts_table(context, chat_id, DC_CONTACT_ID_SELF as u32); } if from_id > DC_CHAT_ID_LAST_SPECIAL as u32 { if !Contact::addr_equals_contact(context, &self_addr, from_id as u32) && (skip.is_null() || !Contact::addr_equals_contact(context, to_string(skip), from_id as u32)) { - dc_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); } } for &to_id in to_ids.iter() { @@ -1424,11 +1422,11 @@ unsafe fn create_or_lookup_group( && (skip.is_null() || !Contact::addr_equals_contact(context, to_string(skip), to_id)) { - dc_add_to_chat_contacts_table(context, chat_id, to_id); + chat::add_to_chat_contacts_table(context, chat_id, to_id); } } send_EVENT_CHAT_MODIFIED = 1; - dc_reset_gossiped_timestamp(context, chat_id); + chat::reset_gossiped_timestamp(context, chat_id); } if 0 != send_EVENT_CHAT_MODIFIED { @@ -1438,7 +1436,7 @@ unsafe fn create_or_lookup_group( // check the number of receivers - // the only critical situation is if the user hits "Reply" instead of "Reply all" in a non-messenger-client */ if to_ids_cnt == 1 && mime_parser.is_send_by_messenger == 0 { - let is_contact_cnt = dc_get_chat_contact_cnt(context, chat_id); + let is_contact_cnt = chat::get_chat_contact_cnt(context, chat_id); if is_contact_cnt > 3 { // to_ids_cnt==1 may be "From: A, To: B, SELF" as SELF is not counted in to_ids_cnt. // So everything up to 3 is no error. @@ -1457,7 +1455,6 @@ unsafe fn create_or_lookup_group( } cleanup( - grpid, grpname, failure_reason, ret_chat_id, @@ -1472,41 +1469,35 @@ unsafe fn create_or_lookup_adhoc_group( context: &Context, mime_parser: &dc_mimeparser_t, allow_creation: libc::c_int, - create_blocked: libc::c_int, + create_blocked: Blocked, from_id: u32, to_ids: &mut Vec, ret_chat_id: *mut uint32_t, - ret_chat_id_blocked: *mut libc::c_int, + ret_chat_id_blocked: &mut Blocked, ) { // if we're here, no grpid was found, check there is an existing ad-hoc // group matching the to-list or if we can create one let mut chat_id = 0; - let mut chat_id_blocked = 0; - let mut grpid = 0 as *mut libc::c_char; + let mut chat_id_blocked = Blocked::Not; let mut grpname = 0 as *mut libc::c_char; - let cleanup = |grpid: *mut libc::c_char, - grpname: *mut libc::c_char, + let cleanup = |grpname: *mut libc::c_char, ret_chat_id: *mut uint32_t, - ret_chat_id_blocked: *mut libc::c_int, + ret_chat_id_blocked: &mut Blocked, chat_id: u32, - chat_id_blocked: i32| { - free(grpid as *mut libc::c_void); + chat_id_blocked: Blocked| { free(grpname as *mut libc::c_void); if !ret_chat_id.is_null() { - *ret_chat_id = chat_id + *ret_chat_id = chat_id; } - if !ret_chat_id_blocked.is_null() { - *ret_chat_id_blocked = chat_id_blocked - }; + *ret_chat_id_blocked = chat_id_blocked; }; // build member list from the given ids if to_ids.is_empty() || 0 != dc_mimeparser_is_mailinglist_message(mime_parser) { // too few contacts or a mailinglist cleanup( - grpid, grpname, ret_chat_id, ret_chat_id_blocked, @@ -1526,7 +1517,6 @@ unsafe fn create_or_lookup_adhoc_group( if member_ids.len() < 3 { // too few contacts given cleanup( - grpid, grpname, ret_chat_id, ret_chat_id_blocked, @@ -1547,7 +1537,7 @@ unsafe fn create_or_lookup_adhoc_group( ), params![], |row| { - Ok((row.get::<_, i32>(0)?, row.get::<_, Option>(1)?.unwrap_or_default())) + Ok((row.get::<_, i32>(0)?, row.get::<_, Option>(1)?.unwrap_or_default())) } ); @@ -1556,7 +1546,6 @@ unsafe fn create_or_lookup_adhoc_group( chat_id_blocked = id_blocked; /* success, chat found */ cleanup( - grpid, grpname, ret_chat_id, ret_chat_id_blocked, @@ -1569,7 +1558,6 @@ unsafe fn create_or_lookup_adhoc_group( if 0 == allow_creation { cleanup( - grpid, grpname, ret_chat_id, ret_chat_id_blocked, @@ -1583,10 +1571,9 @@ unsafe fn create_or_lookup_adhoc_group( // create a new ad-hoc group // - there is no need to check if this group exists; otherwise we would have caught it above - grpid = create_adhoc_grp_id(context, &member_ids); - if grpid.is_null() { + let grpid = create_adhoc_grp_id(context, &member_ids); + if grpid.is_empty() { cleanup( - grpid, grpname, ret_chat_id, ret_chat_id_blocked, @@ -1606,16 +1593,21 @@ unsafe fn create_or_lookup_adhoc_group( } // create group record - chat_id = create_group_record(context, grpid, grpname, create_blocked, 0); + chat_id = create_group_record( + context, + &grpid, + grpname, + create_blocked, + VerifiedStatus::Unverified, + ); chat_id_blocked = create_blocked; for &member_id in &member_ids { - dc_add_to_chat_contacts_table(context, chat_id, member_id); + chat::add_to_chat_contacts_table(context, chat_id, member_id); } context.call_cb(Event::CHAT_MODIFIED, chat_id as uintptr_t, 0 as uintptr_t); cleanup( - grpid, grpname, ret_chat_id, ret_chat_id_blocked, @@ -1626,19 +1618,23 @@ unsafe fn create_or_lookup_adhoc_group( fn create_group_record( context: &Context, - grpid: *const libc::c_char, + grpid: impl AsRef, grpname: *const libc::c_char, - create_blocked: libc::c_int, - create_verified: libc::c_int, + create_blocked: Blocked, + create_verified: VerifiedStatus, ) -> u32 { if sql::execute( context, &context.sql, "INSERT INTO chats (type, name, grpid, blocked) VALUES(?, ?, ?, ?);", params![ - if 0 != create_verified { 130 } else { 120 }, + if VerifiedStatus::Unverified != create_verified { + Chattype::VerifiedGroup + } else { + Chattype::Group + }, as_str(grpname), - as_str(grpid), + grpid.as_ref(), create_blocked, ], ) @@ -1647,10 +1643,10 @@ fn create_group_record( return 0; } - sql::get_rowid(context, &context.sql, "chats", "grpid", as_str(grpid)) + sql::get_rowid(context, &context.sql, "chats", "grpid", grpid.as_ref()) } -unsafe fn create_adhoc_grp_id(context: &Context, member_ids: &Vec) -> *mut libc::c_char { +fn create_adhoc_grp_id(context: &Context, member_ids: &Vec) -> String { /* algorithm: - sort normalized, lowercased, e-mail addresses alphabetically - put all e-mail addresses into a single string, separate the address by a single comma @@ -1686,14 +1682,13 @@ unsafe fn create_adhoc_grp_id(context: &Context, member_ids: &Vec) -> *mut ) .unwrap_or_else(|_| member_cs); - hex_hash(&members) as *mut _ + hex_hash(&members) } -fn hex_hash(s: impl AsRef) -> *const libc::c_char { +fn hex_hash(s: impl AsRef) -> String { let bytes = s.as_ref().as_bytes(); let result = Sha256::digest(bytes); - let result_hex = hex::encode(&result[..8]); - unsafe { result_hex.strdup() as *const _ } + hex::encode(&result[..8]) } #[allow(non_snake_case)] @@ -2170,8 +2165,7 @@ mod tests { fn test_hex_hash() { let data = "hello world"; - let res_c = hex_hash(data); - let res = to_string(res_c); + let res = hex_hash(data); assert_eq!(res, "b94d27b9934d3e08"); } } diff --git a/src/dc_securejoin.rs b/src/dc_securejoin.rs index 0b5fb2a92..11a162e2b 100644 --- a/src/dc_securejoin.rs +++ b/src/dc_securejoin.rs @@ -1,20 +1,17 @@ -use std::ffi::CString; - use mmime::mailimf_types::*; use percent_encoding::{utf8_percent_encode, NON_ALPHANUMERIC}; use crate::aheader::EncryptPreference; +use crate::chat::{self, Chat}; use crate::constants::*; use crate::contact::*; use crate::context::Context; -use crate::dc_chat::*; use crate::dc_configure::*; use crate::dc_e2ee::*; use crate::dc_lot::*; use crate::dc_mimeparser::*; use crate::dc_msg::*; use crate::dc_qr::*; -use crate::dc_strencode::*; use crate::dc_token::*; use crate::dc_tools::*; use crate::key::*; @@ -36,9 +33,6 @@ pub unsafe fn dc_get_securejoin_qr( let mut fingerprint = 0 as *mut libc::c_char; let mut invitenumber: *mut libc::c_char; let mut auth: *mut libc::c_char; - let mut chat = 0 as *mut Chat; - let mut group_name = 0 as *mut libc::c_char; - let mut group_name_urlencoded = 0 as *mut libc::c_char; let mut qr: Option = None; dc_ensure_secret_key_exists(context).ok(); @@ -54,13 +48,10 @@ pub unsafe fn dc_get_securejoin_qr( } let self_addr = context.sql.get_config(context, "configured_addr"); - let cleanup = |fingerprint, chat, group_name, group_name_urlencoded| { + let cleanup = |fingerprint| { free(fingerprint as *mut libc::c_void); free(invitenumber as *mut libc::c_void); free(auth as *mut libc::c_void); - dc_chat_unref(chat); - free(group_name as *mut libc::c_void); - free(group_name_urlencoded as *mut libc::c_void); if let Some(qr) = qr { qr.strdup() @@ -71,7 +62,7 @@ pub unsafe fn dc_get_securejoin_qr( if self_addr.is_none() { error!(context, 0, "Not configured, cannot generate QR code.",); - return cleanup(fingerprint, chat, group_name, group_name_urlencoded); + return cleanup(fingerprint); } let self_addr = self_addr.unwrap(); @@ -83,34 +74,34 @@ pub unsafe fn dc_get_securejoin_qr( fingerprint = get_self_fingerprint(context); if fingerprint.is_null() { - return cleanup(fingerprint, chat, group_name, group_name_urlencoded); + return cleanup(fingerprint); } let self_addr_urlencoded = utf8_percent_encode(&self_addr, NON_ALPHANUMERIC).to_string(); let self_name_urlencoded = utf8_percent_encode(&self_name, NON_ALPHANUMERIC).to_string(); qr = if 0 != group_chat_id { - chat = dc_get_chat(context, group_chat_id); - if chat.is_null() { + if let Ok(chat) = Chat::load_from_db(context, group_chat_id) { + let group_name = chat.get_name(); + let group_name_urlencoded = + utf8_percent_encode(&group_name, NON_ALPHANUMERIC).to_string(); + + Some(format!( + "OPENPGP4FPR:{}#a={}&g={}&x={}&i={}&s={}", + as_str(fingerprint), + self_addr_urlencoded, + &group_name_urlencoded, + &chat.grpid, + as_str(invitenumber), + as_str(auth), + )) + } else { error!( context, 0, "Cannot get QR-code for chat-id {}", group_chat_id, ); - return cleanup(fingerprint, chat, group_name, group_name_urlencoded); + return cleanup(fingerprint); } - - group_name = dc_chat_get_name(chat); - group_name_urlencoded = dc_urlencode(group_name); - - Some(format!( - "OPENPGP4FPR:{}#a={}&g={}&x={}&i={}&s={}", - as_str(fingerprint), - self_addr_urlencoded, - as_str(group_name_urlencoded), - as_str((*chat).grpid), - as_str(invitenumber), - as_str(auth), - )) } else { Some(format!( "OPENPGP4FPR:{}#a={}&n={}&i={}&s={}", @@ -124,7 +115,7 @@ pub unsafe fn dc_get_securejoin_qr( info!(context, 0, "Generated QR code: {}", qr.as_ref().unwrap()); - cleanup(fingerprint, chat, group_name, group_name_urlencoded) + cleanup(fingerprint) } fn get_self_fingerprint(context: &Context) -> *mut libc::c_char { @@ -155,7 +146,8 @@ pub unsafe fn dc_join_securejoin(context: &Context, qr: *const libc::c_char) -> if qr_scan.is_null() || (*qr_scan).state != 200i32 && (*qr_scan).state != 202i32 { error!(context, 0, "Unknown QR code.",); } else { - contact_chat_id = dc_create_chat_by_contact_id(context, (*qr_scan).id); + contact_chat_id = + chat::create_by_contact_id(context, (*qr_scan).id).unwrap_or_default(); if contact_chat_id == 0i32 as libc::c_uint { error!(context, 0, "Unknown contact.",); } else if !(context @@ -193,9 +185,9 @@ pub unsafe fn dc_join_securejoin(context: &Context, qr: *const libc::c_char) -> (*qr_scan).auth, own_fingerprint, if 0 != join_vg { - (*qr_scan).text2 + as_str((*qr_scan).text2) } else { - 0 as *mut libc::c_char + "" }, ); free(own_fingerprint as *mut libc::c_void); @@ -211,7 +203,7 @@ pub unsafe fn dc_join_securejoin(context: &Context, qr: *const libc::c_char) -> }, (*qr_scan).invitenumber, 0 as *const libc::c_char, - 0 as *const libc::c_char, + "", ); } @@ -234,10 +226,10 @@ pub unsafe fn dc_join_securejoin(context: &Context, qr: *const libc::c_char) -> bob.expects = 0; if bob.status == 1 { if 0 != join_vg { - ret_chat_id = dc_get_chat_id_by_grpid( + ret_chat_id = chat::get_chat_id_by_grpid( context, - (*qr_scan).text2, - 0 as *mut libc::c_int, + to_string((*qr_scan).text2), + None, 0 as *mut libc::c_int, ) as libc::c_int } else { @@ -259,7 +251,7 @@ unsafe fn send_handshake_msg( step: *const libc::c_char, param2: *const libc::c_char, fingerprint: *const libc::c_char, - grpid: *const libc::c_char, + grpid: impl AsRef, ) { let mut msg: *mut dc_msg_t = dc_msg_new_untyped(context); (*msg).type_0 = Viewtype::Text; @@ -277,8 +269,8 @@ unsafe fn send_handshake_msg( if !fingerprint.is_null() { (*msg).param.set(Param::Arg3, as_str(fingerprint)); } - if !grpid.is_null() { - (*msg).param.set(Param::Arg4, as_str(grpid)); + if !grpid.as_ref().is_empty() { + (*msg).param.set(Param::Arg4, grpid.as_ref()); } if strcmp(step, b"vg-request\x00" as *const u8 as *const libc::c_char) == 0i32 || strcmp(step, b"vc-request\x00" as *const u8 as *const libc::c_char) == 0i32 @@ -290,12 +282,13 @@ unsafe fn send_handshake_msg( } else { (*msg).param.set_int(Param::GuranteeE2ee, 1); } - dc_send_msg(context, contact_chat_id, msg); + // TODO. handle cleanup on error + chat::send_msg(context, contact_chat_id, msg).unwrap(); dc_msg_unref(msg); } unsafe fn chat_id_2_contact_id(context: &Context, contact_chat_id: uint32_t) -> uint32_t { - let contacts = dc_get_chat_contacts(context, contact_chat_id); + let contacts = chat::get_chat_contacts(context, contact_chat_id); if contacts.len() == 1 { contacts[0] } else { @@ -312,7 +305,7 @@ unsafe fn fingerprint_equals_sender( return 0; } let mut fingerprint_equal: libc::c_int = 0i32; - let contacts = dc_get_chat_contacts(context, contact_chat_id); + let contacts = chat::get_chat_contacts(context, contact_chat_id); if contacts.len() == 1 { if let Ok(contact) = Contact::load_from_db(context, contacts[0]) { @@ -345,9 +338,9 @@ pub unsafe fn dc_handle_securejoin_handshake( let mut scanned_fingerprint_of_alice: *mut libc::c_char = 0 as *mut libc::c_char; let mut auth: *mut libc::c_char = 0 as *mut libc::c_char; let mut own_fingerprint: *mut libc::c_char = 0 as *mut libc::c_char; - let mut contact_chat_id: uint32_t = 0i32 as uint32_t; - let mut contact_chat_id_blocked: libc::c_int = 0i32; - let mut grpid: *mut libc::c_char = 0 as *mut libc::c_char; + let contact_chat_id: u32; + let contact_chat_id_blocked: Blocked; + let mut grpid = "".to_string(); let mut ret: libc::c_int = 0i32; if !(contact_id <= 9i32 as libc::c_uint) { @@ -361,15 +354,13 @@ pub unsafe fn dc_handle_securejoin_handshake( ); join_vg = (strncmp(step, b"vg-\x00" as *const u8 as *const libc::c_char, 3) == 0) as libc::c_int; - dc_create_or_lookup_nchat_by_contact_id( - context, - contact_id, - 0i32, - &mut contact_chat_id, - &mut contact_chat_id_blocked, - ); - if 0 != contact_chat_id_blocked { - dc_unblock_chat(context, contact_chat_id); + let (id, bl) = chat::create_or_lookup_by_contact_id(context, contact_id, Blocked::Not) + .unwrap_or_default(); + contact_chat_id = id; + contact_chat_id_blocked = bl; + + if Blocked::Not != contact_chat_id_blocked { + chat::unblock(context, contact_chat_id); } ret = 0x2i32; if strcmp(step, b"vg-request\x00" as *const u8 as *const libc::c_char) == 0i32 @@ -409,7 +400,7 @@ pub unsafe fn dc_handle_securejoin_handshake( }, 0 as *const libc::c_char, 0 as *const libc::c_char, - 0 as *const libc::c_char, + "", ); current_block = 10256747982273457880; } @@ -439,7 +430,7 @@ pub unsafe fn dc_handle_securejoin_handshake( scanned_fingerprint_of_alice = dc_strdup((*scan).fingerprint); auth = dc_strdup((*scan).auth); if 0 != join_vg { - grpid = dc_strdup((*scan).text2) + grpid = to_string((*scan).text2); } } if !encrypted_and_signed(mimeparser, scanned_fingerprint_of_alice) { @@ -579,18 +570,18 @@ pub unsafe fn dc_handle_securejoin_handshake( 600i32 as uintptr_t, ); if 0 != join_vg { - grpid = dc_strdup(lookup_field(mimeparser, "Secure-Join-Group")); - let group_chat_id: uint32_t = dc_get_chat_id_by_grpid( + grpid = to_string(lookup_field(mimeparser, "Secure-Join-Group")); + let group_chat_id: uint32_t = chat::get_chat_id_by_grpid( context, - grpid, - 0 as *mut libc::c_int, + &grpid, + None, 0 as *mut libc::c_int, ); if group_chat_id == 0i32 as libc::c_uint { - error!(context, 0, "Chat {} not found.", as_str(grpid),); + error!(context, 0, "Chat {} not found.", &grpid); current_block = 4378276786830486580; } else { - dc_add_contact_to_chat_ex( + chat::add_contact_to_chat_ex( context, group_chat_id, contact_id, @@ -605,7 +596,7 @@ pub unsafe fn dc_handle_securejoin_handshake( b"vc-contact-confirm\x00" as *const u8 as *const libc::c_char, 0 as *const libc::c_char, 0 as *const libc::c_char, - 0 as *const libc::c_char, + "", ); context.call_cb( Event::SECUREJOIN_INVITER_PROGRESS, @@ -647,16 +638,16 @@ pub unsafe fn dc_handle_securejoin_handshake( let scan = context.bob.clone().read().unwrap().qr_scan; scanned_fingerprint_of_alice = dc_strdup((*scan).fingerprint); if 0 != join_vg { - grpid = dc_strdup((*scan).text2) + grpid = to_string((*scan).text2); } } let mut vg_expect_encrypted: libc::c_int = 1i32; if 0 != join_vg { let mut is_verified_group: libc::c_int = 0i32; - dc_get_chat_id_by_grpid( + chat::get_chat_id_by_grpid( context, grpid, - 0 as *mut libc::c_int, + None, &mut is_verified_group, ); if 0 == is_verified_group { @@ -735,7 +726,7 @@ pub unsafe fn dc_handle_securejoin_handshake( as *const libc::c_char, 0 as *const libc::c_char, 0 as *const libc::c_char, - 0 as *const libc::c_char, + "", ); } end_bobs_joining(context, 1i32); @@ -794,7 +785,6 @@ pub unsafe fn dc_handle_securejoin_handshake( free(scanned_fingerprint_of_alice as *mut libc::c_void); free(auth as *mut libc::c_void); free(own_fingerprint as *mut libc::c_void); - free(grpid as *mut libc::c_void); ret } @@ -812,9 +802,8 @@ unsafe fn secure_connection_established(context: &Context, contact_chat_id: uint } else { "?" }; - let msg = - CString::new(context.stock_string_repl_str(StockMessage::ContactVerified, addr)).unwrap(); - dc_add_device_msg(context, contact_chat_id, msg.as_ptr()); + let msg = context.stock_string_repl_str(StockMessage::ContactVerified, addr); + chat::add_device_msg(context, contact_chat_id, msg); context.call_cb( Event::CHAT_MODIFIED, contact_chat_id as uintptr_t, @@ -854,9 +843,9 @@ unsafe fn could_not_establish_secure_connection( "?" }, ); - let msg_c = CString::new(msg.as_str()).unwrap(); - dc_add_device_msg(context, contact_chat_id, msg_c.as_ptr()); - error!(context, 0, "{} ({})", msg, as_str(details)); + + chat::add_device_msg(context, contact_chat_id, &msg); + error!(context, 0, "{} ({})", &msg, as_str(details)); } unsafe fn mark_peer_as_verified( @@ -917,8 +906,6 @@ unsafe fn encrypted_and_signed( } pub unsafe fn dc_handle_degrade_event(context: &Context, peerstate: &Peerstate) { - let mut contact_chat_id = 0; - // - 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 @@ -935,22 +922,17 @@ pub unsafe fn dc_handle_degrade_event(context: &Context, peerstate: &Peerstate) ) .unwrap_or_default(); if contact_id > 0 { - dc_create_or_lookup_nchat_by_contact_id( - context, - contact_id as u32, - 2, - &mut contact_chat_id, - 0 as *mut libc::c_int, - ); + let (contact_chat_id, _) = + chat::create_or_lookup_by_contact_id(context, contact_id as u32, Blocked::Deaddrop) + .unwrap_or_default(); + let peeraddr: &str = match peerstate.addr { Some(ref addr) => &addr, None => "", }; - let msg = CString::new( - context.stock_string_repl_str(StockMessage::ContactSetupChanged, peeraddr), - ) - .unwrap(); - dc_add_device_msg(context, contact_chat_id, msg.as_ptr()); + let msg = context.stock_string_repl_str(StockMessage::ContactSetupChanged, peeraddr); + + chat::add_device_msg(context, contact_chat_id, msg); context.call_cb( Event::CHAT_MODIFIED, contact_chat_id as uintptr_t, diff --git a/src/dc_strencode.rs b/src/dc_strencode.rs index 4aff165d3..41b8bc553 100644 --- a/src/dc_strencode.rs +++ b/src/dc_strencode.rs @@ -18,48 +18,6 @@ fn isalnum(c: libc::c_int) -> libc::c_int { } } -pub unsafe fn dc_urlencode(to_encode: *const libc::c_char) -> *mut libc::c_char { - let mut pstr: *const libc::c_char = to_encode; - if to_encode.is_null() { - return dc_strdup(b"\x00" as *const u8 as *const libc::c_char); - } - let buf: *mut libc::c_char = - malloc(strlen(to_encode).wrapping_mul(3).wrapping_add(1)) as *mut libc::c_char; - let mut pbuf: *mut libc::c_char = buf; - assert!(!buf.is_null()); - - while 0 != *pstr { - if 0 != isalnum(*pstr as libc::c_int) - || *pstr as libc::c_int == '-' as i32 - || *pstr as libc::c_int == '_' as i32 - || *pstr as libc::c_int == '.' as i32 - || *pstr as libc::c_int == '~' as i32 - { - let fresh0 = pbuf; - pbuf = pbuf.offset(1); - *fresh0 = *pstr - } else if *pstr as libc::c_int == ' ' as i32 { - let fresh1 = pbuf; - pbuf = pbuf.offset(1); - *fresh1 = '+' as i32 as libc::c_char - } else { - let fresh2 = pbuf; - pbuf = pbuf.offset(1); - *fresh2 = '%' as i32 as libc::c_char; - let fresh3 = pbuf; - pbuf = pbuf.offset(1); - *fresh3 = int_2_uppercase_hex((*pstr as libc::c_int >> 4i32) as libc::c_char); - let fresh4 = pbuf; - pbuf = pbuf.offset(1); - *fresh4 = int_2_uppercase_hex((*pstr as libc::c_int & 15i32) as libc::c_char) - } - pstr = pstr.offset(1isize) - } - *pbuf = '\u{0}' as i32 as libc::c_char; - - buf -} - /* ****************************************************************************** * URL encoding and decoding, RFC 3986 ******************************************************************************/ @@ -713,6 +671,7 @@ unsafe fn print_hex(target: *mut libc::c_char, cur: *const libc::c_char) { #[cfg(test)] mod tests { use super::*; + use percent_encoding::{percent_encode, NON_ALPHANUMERIC}; use std::ffi::CStr; #[test] @@ -892,14 +851,15 @@ mod tests { #[test] fn test_dc_urlencode_urldecode() { unsafe { - let buf1 = - dc_urlencode(b"Bj\xc3\xb6rn Petersen\x00" as *const u8 as *const libc::c_char); + let buf1 = percent_encode(b"Bj\xc3\xb6rn Petersen", NON_ALPHANUMERIC) + .to_string() + .strdup(); assert_eq!( CStr::from_ptr(buf1 as *const libc::c_char) .to_str() .unwrap(), - "Bj%C3%B6rn+Petersen" + "Bj%C3%B6rn%20Petersen" ); let buf2 = dc_urldecode(buf1); diff --git a/src/dc_tools.rs b/src/dc_tools.rs index 22ac09582..87d8ec9bb 100644 --- a/src/dc_tools.rs +++ b/src/dc_tools.rs @@ -1,5 +1,6 @@ use std::borrow::Cow; use std::ffi::{CStr, CString}; +use std::path::Path; use std::str::FromStr; use std::time::SystemTime; use std::{fmt, fs, ptr}; @@ -166,11 +167,6 @@ pub unsafe fn dc_trim(buf: *mut libc::c_char) { dc_rtrim(buf); } -/* the result must be free()'d */ -unsafe fn dc_strlower(in_0: *const libc::c_char) -> *mut libc::c_char { - to_string(in_0).to_lowercase().strdup() -} - pub unsafe fn dc_strlower_in_place(in_0: *mut libc::c_char) { let raw = CString::yolo(to_string(in_0).to_lowercase()); assert_eq!(strlen(in_0), strlen(raw.as_ptr())); @@ -531,7 +527,7 @@ const COLORS: [u32; 16] = [ 0x39b249, 0xbb243b, 0x964078, 0x66874f, 0x308ab9, 0x127ed0, 0xbe450c, ]; -pub fn dc_str_to_color_safe(s: impl AsRef) -> u32 { +pub fn dc_str_to_color(s: impl AsRef) -> u32 { let str_lower = s.as_ref().to_lowercase(); let mut checksum = 0; let bytes = str_lower.as_bytes(); @@ -544,10 +540,6 @@ pub fn dc_str_to_color_safe(s: impl AsRef) -> u32 { COLORS[color_index] } -pub unsafe fn dc_str_to_color(str: *const libc::c_char) -> libc::c_int { - dc_str_to_color_safe(as_str(str)) as libc::c_int -} - /* clist tools */ /* calls free() for each item content */ pub unsafe fn clist_free_content(haystack: *const clist) { @@ -637,7 +629,7 @@ pub unsafe fn dc_smeared_time(context: &Context) -> i64 { now } -pub unsafe fn dc_create_smeared_timestamp(context: &Context) -> i64 { +pub fn dc_create_smeared_timestamp(context: &Context) -> i64 { let now = time(); let mut ret = now; @@ -652,7 +644,7 @@ pub unsafe fn dc_create_smeared_timestamp(context: &Context) -> i64 { ret } -pub unsafe fn dc_create_smeared_timestamps(context: &Context, count: libc::c_int) -> i64 { +pub fn dc_create_smeared_timestamps(context: &Context, count: libc::c_int) -> i64 { /* get a range to timestamps that can be used uniquely */ let now = time(); let start = now + (if count < 5 { count } else { 5 }) as i64 - count as i64; @@ -848,17 +840,11 @@ unsafe fn dc_validate_filename(filename: *mut libc::c_char) { } } -#[allow(non_snake_case)] -pub unsafe fn dc_get_filename(pathNfilename: *const libc::c_char) -> *mut libc::c_char { - let mut p: *const libc::c_char = strrchr(pathNfilename, '/' as i32); - if p.is_null() { - p = strrchr(pathNfilename, '\\' as i32) - } - if !p.is_null() { - p = p.offset(1isize); - dc_strdup(p) +pub unsafe fn dc_get_filename(path_filename: impl AsRef) -> *mut libc::c_char { + if let Some(p) = Path::new(path_filename.as_ref()).file_name() { + p.to_string_lossy().strdup() } else { - dc_strdup(pathNfilename) + ptr::null_mut() } } @@ -869,12 +855,15 @@ unsafe fn dc_split_filename( ret_basename: *mut *mut libc::c_char, ret_all_suffixes_incl_dot: *mut *mut libc::c_char, ) { + if pathNfilename.is_null() { + return; + } /* splits a filename into basename and all suffixes, eg. "/path/foo.tar.gz" is split into "foo.tar" and ".gz", (we use the _last_ dot which allows the usage inside the filename which are very usual; maybe the detection could be more intelligent, however, for the moment, it is just file) - if there is no suffix, the returned suffix string is empty, eg. "/path/foobar" is split into "foobar" and "" - the case of the returned suffix is preserved; this is to allow reconstruction of (similar) names */ - let basename: *mut libc::c_char = dc_get_filename(pathNfilename); + let basename: *mut libc::c_char = dc_get_filename(as_str(pathNfilename)); let suffix: *mut libc::c_char; let p1: *mut libc::c_char = strrchr(basename, '.' as i32); if !p1.is_null() { @@ -897,16 +886,12 @@ unsafe fn dc_split_filename( // the returned suffix is lower-case #[allow(non_snake_case)] -pub unsafe fn dc_get_filesuffix_lc(pathNfilename: *const libc::c_char) -> *mut libc::c_char { - if !pathNfilename.is_null() { - let mut p: *const libc::c_char = strrchr(pathNfilename, '.' as i32); - if !p.is_null() { - p = p.offset(1isize); - return dc_strlower(p); - } +pub unsafe fn dc_get_filesuffix_lc(path_filename: impl AsRef) -> *mut libc::c_char { + if let Some(p) = Path::new(path_filename.as_ref()).extension() { + p.to_string_lossy().to_lowercase().strdup() + } else { + ptr::null_mut() } - - ptr::null_mut() } /// Returns the `(width, height)` of the given image buffer. @@ -936,34 +921,25 @@ pub fn dc_get_abs_path_safe>( } } -#[allow(non_snake_case)] pub unsafe fn dc_get_abs_path( context: &Context, - pathNfilename: *const libc::c_char, + path_filename: impl AsRef, ) -> *mut libc::c_char { - if pathNfilename.is_null() { - return ptr::null_mut(); - } - - let starts = strncmp( - pathNfilename, - b"$BLOBDIR\x00" as *const u8 as *const libc::c_char, - 8, - ) == 0; + let starts = path_filename.as_ref().starts_with("$BLOBDIR"); if starts && !context.has_blobdir() { return ptr::null_mut(); } - let mut pathNfilename_abs: *mut libc::c_char = dc_strdup(pathNfilename); + let mut path_filename_abs = path_filename.as_ref().strdup(); if starts && context.has_blobdir() { dc_str_replace( - &mut pathNfilename_abs, + &mut path_filename_abs, b"$BLOBDIR\x00" as *const u8 as *const libc::c_char, context.get_blobdir(), ); } - pathNfilename_abs + path_filename_abs } pub fn dc_file_exist(context: &Context, path: impl AsRef) -> bool { @@ -1159,56 +1135,49 @@ pub unsafe fn dc_get_fine_pathNfilename( ret } -pub unsafe fn dc_is_blobdir_path(context: &Context, path: *const libc::c_char) -> bool { - strncmp(path, context.get_blobdir(), strlen(context.get_blobdir())) == 0 - || strncmp(path, b"$BLOBDIR\x00" as *const u8 as *const libc::c_char, 8) == 0 +pub fn dc_is_blobdir_path(context: &Context, path: impl AsRef) -> bool { + path.as_ref().starts_with(as_str(context.get_blobdir())) + || path.as_ref().starts_with("$BLOBDIR") } -unsafe fn dc_make_rel_path(context: &Context, path: *mut *mut libc::c_char) { - if path.is_null() || (*path).is_null() { - return; +fn dc_make_rel_path(context: &Context, path: &mut String) { + if path.starts_with(as_str(context.get_blobdir())) { + *path = path.replace("$BLOBDIR", as_str(context.get_blobdir())); } - if strncmp(*path, context.get_blobdir(), strlen(context.get_blobdir())) == 0 { - dc_str_replace( - path, - context.get_blobdir(), - b"$BLOBDIR\x00" as *const u8 as *const libc::c_char, - ); - }; } -pub unsafe fn dc_make_rel_and_copy(context: &Context, path: *mut *mut libc::c_char) -> bool { +pub fn dc_make_rel_and_copy(context: &Context, path: &mut String) -> bool { let mut success = false; - let mut filename: *mut libc::c_char = ptr::null_mut(); - let mut blobdir_path: *mut libc::c_char = ptr::null_mut(); - if !(path.is_null() || (*path).is_null()) { - if dc_is_blobdir_path(context, *path) { - dc_make_rel_path(context, path); - success = true; - } else { - filename = dc_get_filename(*path); - if !(filename.is_null() - || { - blobdir_path = dc_get_fine_pathNfilename( + let mut filename = ptr::null_mut(); + let mut blobdir_path = ptr::null_mut(); + if dc_is_blobdir_path(context, &path) { + dc_make_rel_path(context, path); + success = true; + } else { + filename = unsafe { dc_get_filename(&path) }; + if !(filename.is_null() + || { + blobdir_path = unsafe { + dc_get_fine_pathNfilename( context, b"$BLOBDIR\x00" as *const u8 as *const libc::c_char, filename, - ); - blobdir_path.is_null() - } - || !dc_copy_file(context, as_path(*path), as_path(blobdir_path))) - { - free(*path as *mut libc::c_void); - *path = blobdir_path; - blobdir_path = ptr::null_mut(); - dc_make_rel_path(context, path); - success = true; + ) + }; + blobdir_path.is_null() } + || !dc_copy_file(context, &path, as_path(blobdir_path))) + { + *path = to_string(blobdir_path); + blobdir_path = ptr::null_mut(); + dc_make_rel_path(context, path); + success = true; } } - free(blobdir_path as *mut libc::c_void); - free(filename as *mut libc::c_void); - + unsafe { + free(blobdir_path.cast()); + free(filename.cast()); + } success } diff --git a/src/lib.rs b/src/lib.rs index b2d7b4888..299e63cd6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -23,6 +23,7 @@ mod log; mod error; mod aheader; +pub mod chat; pub mod chatlist; pub mod config; pub mod constants; @@ -42,7 +43,6 @@ pub mod types; pub mod x; pub mod dc_array; -pub mod dc_chat; pub mod dc_configure; mod dc_dehtml; mod dc_e2ee; diff --git a/src/peerstate.rs b/src/peerstate.rs index 4f6ba5406..b203904c7 100644 --- a/src/peerstate.rs +++ b/src/peerstate.rs @@ -4,9 +4,9 @@ use std::fmt; use num_traits::FromPrimitive; use crate::aheader::*; +use crate::chat::*; use crate::constants::*; use crate::context::Context; -use crate::dc_chat::*; use crate::key::*; use crate::sql::{self, Sql}; @@ -466,7 +466,7 @@ impl<'a> Peerstate<'a> { } if self.to_save == Some(ToSave::All) || create { - dc_reset_gossiped_timestamp(self.context, 0); + reset_gossiped_timestamp(self.context, 0); } success diff --git a/tests/stress.rs b/tests/stress.rs index 90b93aec6..3ab927d14 100644 --- a/tests/stress.rs +++ b/tests/stress.rs @@ -6,11 +6,11 @@ use std::ffi::CString; use mmime::mailimf_types::*; use tempfile::{tempdir, TempDir}; +use deltachat::chat::{self, Chat}; use deltachat::config; use deltachat::constants::*; use deltachat::contact::*; use deltachat::context::*; -use deltachat::dc_chat::*; use deltachat::dc_configure::*; use deltachat::dc_imex::*; use deltachat::dc_lot::*; @@ -66,15 +66,9 @@ unsafe fn stress_functions(context: &Context) { context.get_blobdir(), b"foobar\x00" as *const u8 as *const libc::c_char, ); - assert!(dc_is_blobdir_path(context, abs_path)); - assert!(dc_is_blobdir_path( - context, - b"$BLOBDIR/fofo\x00" as *const u8 as *const libc::c_char, - )); - assert!(!dc_is_blobdir_path( - context, - b"/BLOBDIR/fofo\x00" as *const u8 as *const libc::c_char, - )); + assert!(dc_is_blobdir_path(context, as_str(abs_path))); + assert!(dc_is_blobdir_path(context, "$BLOBDIR/fofo",)); + assert!(!dc_is_blobdir_path(context, "/BLOBDIR/fofo",)); assert!(dc_file_exist(context, as_path(abs_path))); free(abs_path as *mut libc::c_void); assert!(dc_copy_file(context, "$BLOBDIR/foobar", "$BLOBDIR/dada",)); @@ -765,17 +759,15 @@ fn test_chat() { let contact1 = Contact::create(&context.ctx, "bob", "bob@mail.de").unwrap(); assert_ne!(contact1, 0); - let chat_id = dc_create_chat_by_contact_id(&context.ctx, contact1); + let chat_id = chat::create_by_contact_id(&context.ctx, contact1).unwrap(); assert!(chat_id > 9, "chat_id too small {}", chat_id); - let chat = dc_chat_new(&context.ctx); - assert!(dc_chat_load_from_db(chat, chat_id)); + let chat = Chat::load_from_db(&context.ctx, chat_id).unwrap(); - let chat2_id = dc_create_chat_by_contact_id(&context.ctx, contact1); + let chat2_id = chat::create_by_contact_id(&context.ctx, contact1).unwrap(); assert_eq!(chat2_id, chat_id); - let chat2 = dc_chat_new(&context.ctx); - assert!(dc_chat_load_from_db(chat2, chat2_id)); + let chat2 = Chat::load_from_db(&context.ctx, chat2_id).unwrap(); - assert_eq!(as_str((*chat2).name), as_str((*chat).name)); + assert_eq!(chat2.name, chat.name); } }