diff --git a/deltachat-ffi/src/lib.rs b/deltachat-ffi/src/lib.rs index d3c53f556..8d13539b5 100644 --- a/deltachat-ffi/src/lib.rs +++ b/deltachat-ffi/src/lib.rs @@ -322,7 +322,11 @@ pub unsafe extern "C" fn dc_create_chat_by_msg_id(context: *mut dc_context_t, ms assert!(!context.is_null()); let context = &*context; - chat::dc_create_chat_by_msg_id(context, msg_id) + log_err_default( + chat::create_by_msg_id(context, msg_id), + context, + "Failed to create chat", + ) } #[no_mangle] @@ -333,7 +337,11 @@ pub unsafe extern "C" fn dc_create_chat_by_contact_id( assert!(!context.is_null()); let context = &*context; - chat::dc_create_chat_by_contact_id(context, contact_id) + log_err_default( + chat::create_by_contact_id(context, contact_id), + context, + "Failed to create chat", + ) } #[no_mangle] @@ -344,7 +352,11 @@ pub unsafe extern "C" fn dc_get_chat_id_by_contact_id( assert!(!context.is_null()); let context = &*context; - chat::dc_get_chat_id_by_contact_id(context, contact_id) + log_err_default( + chat::get_by_contact_id(context, contact_id), + context, + "Failed to get chat", + ) } #[no_mangle] @@ -357,7 +369,11 @@ pub unsafe extern "C" fn dc_prepare_msg( assert!(!msg.is_null()); let context = &*context; - chat::dc_prepare_msg(context, chat_id, msg) + log_err_default( + chat::prepare_msg(context, chat_id, msg), + context, + "Failed to prepare message", + ) } #[no_mangle] @@ -370,7 +386,11 @@ pub unsafe extern "C" fn dc_send_msg( assert!(!msg.is_null()); let context = &*context; - chat::dc_send_msg(context, chat_id, msg) + log_err_default( + chat::send_msg(context, chat_id, msg), + context, + "Failed to send message", + ) } #[no_mangle] @@ -384,7 +404,11 @@ pub unsafe extern "C" fn dc_send_text_msg( let context = &*context; let text_to_send = dc_tools::to_string_lossy(text_to_send); - chat::dc_send_text_msg(context, chat_id, text_to_send) + log_err_default( + chat::send_text_msg(context, chat_id, text_to_send), + context, + "Failed to send text message", + ) } #[no_mangle] @@ -396,7 +420,7 @@ pub unsafe extern "C" fn dc_set_draft( assert!(!context.is_null()); let context = &*context; - chat::dc_set_draft(context, chat_id, msg) + chat::set_draft(context, chat_id, msg) } #[no_mangle] @@ -407,7 +431,7 @@ pub unsafe extern "C" fn dc_get_draft<'a>( assert!(!context.is_null()); let context = &*context; - chat::dc_get_draft(context, chat_id) + chat::get_draft(context, chat_id) } #[no_mangle] @@ -420,7 +444,7 @@ pub unsafe extern "C" fn dc_get_chat_msgs( assert!(!context.is_null()); let context = &*context; - chat::dc_get_chat_msgs(context, chat_id, flags, marker1before) + chat::get_chat_msgs(context, chat_id, flags, marker1before) } #[no_mangle] @@ -428,7 +452,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; - chat::dc_get_msg_cnt(context, chat_id) + chat::get_msg_cnt(context, chat_id) as libc::c_int } #[no_mangle] @@ -439,7 +463,7 @@ pub unsafe extern "C" fn dc_get_fresh_msg_cnt( assert!(!context.is_null()); let context = &*context; - 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 +481,11 @@ pub unsafe extern "C" fn dc_marknoticed_chat(context: *mut dc_context_t, chat_id assert!(!context.is_null()); let context = &*context; - chat::dc_marknoticed_chat(context, chat_id); + log_err( + chat::marknoticed_chat(context, chat_id), + context, + "Failed marknoticed chat", + ); } #[no_mangle] @@ -465,7 +493,11 @@ pub unsafe extern "C" fn dc_marknoticed_all_chats(context: *mut dc_context_t) { assert!(!context.is_null()); let context = &*context; - chat::dc_marknoticed_all_chats(context); + log_err( + chat::marknoticed_all_chats(context), + context, + "Failed marknoticed all chats", + ); } fn from_prim(s: S) -> Option @@ -493,7 +525,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)); - 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 +546,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)); - 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 +558,19 @@ pub unsafe extern "C" fn dc_archive_chat( assert!(!context.is_null()); let context = &*context; - chat::dc_archive_chat(context, chat_id, archive); + let archive = if archive == 0 { + false + } else if archive == 1 { + true + } else { + return; + }; + + log_err( + chat::archive(context, chat_id, archive), + context, + "Failed archive chat", + ); } #[no_mangle] @@ -535,7 +579,11 @@ pub unsafe extern "C" fn dc_delete_chat(context: *mut dc_context_t, chat_id: u32 let context = &*context; // TODO: update to indicate public api success/failure of deletion - chat::dc_delete_chat(context, chat_id); + log_err( + chat::delete(context, chat_id), + context, + "Failed chat delete", + ); } #[no_mangle] @@ -546,7 +594,7 @@ pub unsafe extern "C" fn dc_get_chat_contacts( assert!(!context.is_null()); let context = &*context; - dc_array_t::from(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 +618,7 @@ pub unsafe extern "C" fn dc_get_chat<'a>( assert!(!context.is_null()); let context = &*context; - match 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(), } @@ -586,7 +634,17 @@ pub unsafe extern "C" fn dc_create_group_chat( assert!(!name.is_null()); let context = &*context; - chat::dc_create_group_chat(context, verified, name) + let verified = if let Some(s) = contact::VerifiedStatus::from_i32(verified) { + s + } else { + return 0; + }; + + log_err_default( + chat::create_group_chat(context, verified, name), + context, + "Failed to create group chat", + ) } #[no_mangle] @@ -598,7 +656,7 @@ pub unsafe extern "C" fn dc_is_contact_in_chat( assert!(!context.is_null()); let context = &*context; - chat::dc_is_contact_in_chat(context, chat_id, contact_id) + chat::is_contact_in_chat(context, chat_id, contact_id) } #[no_mangle] @@ -610,7 +668,7 @@ pub unsafe extern "C" fn dc_add_contact_to_chat( assert!(!context.is_null()); let context = &*context; - chat::dc_add_contact_to_chat(context, chat_id, contact_id) + chat::add_contact_to_chat(context, chat_id, contact_id) } #[no_mangle] @@ -622,7 +680,7 @@ pub unsafe extern "C" fn dc_remove_contact_from_chat( assert!(!context.is_null()); let context = &*context; - chat::dc_remove_contact_from_chat(context, chat_id, contact_id) + chat::remove_contact_from_chat(context, chat_id, contact_id) } #[no_mangle] @@ -636,7 +694,7 @@ pub unsafe extern "C" fn dc_set_chat_name( assert!(chat_id > constants::DC_CHAT_ID_LAST_SPECIAL as u32); let context = &*context; - chat::dc_set_chat_name(context, chat_id, name) + chat::set_chat_name(context, chat_id, name) } #[no_mangle] @@ -649,7 +707,7 @@ pub unsafe extern "C" fn dc_set_chat_profile_image( assert!(chat_id > constants::DC_CHAT_ID_LAST_SPECIAL as u32); let context = &*context; - chat::dc_set_chat_profile_image(context, chat_id, image) + chat::set_chat_profile_image(context, chat_id, image) } #[no_mangle] @@ -701,7 +759,7 @@ pub unsafe extern "C" fn dc_forward_msgs( assert!(chat_id > constants::DC_CHAT_ID_LAST_SPECIAL as u32); let context = &*context; - chat::dc_forward_msgs(context, msg_ids, msg_cnt, chat_id) + chat::forward_msgs(context, msg_ids, msg_cnt, chat_id) } #[no_mangle] @@ -1275,7 +1333,7 @@ pub unsafe extern "C" fn dc_chat_get_id(chat: *mut dc_chat_t) -> u32 { assert!(!chat.is_null()); let chat = &*chat; - chat::dc_chat_get_id(chat) + chat.get_id() } #[no_mangle] @@ -1283,7 +1341,7 @@ pub unsafe extern "C" fn dc_chat_get_type(chat: *mut dc_chat_t) -> libc::c_int { assert!(!chat.is_null()); let chat = &*chat; - chat::dc_chat_get_type(chat) as libc::c_int + chat.get_type() as libc::c_int } #[no_mangle] @@ -1291,7 +1349,7 @@ pub unsafe extern "C" fn dc_chat_get_name(chat: *mut dc_chat_t) -> *mut libc::c_ assert!(!chat.is_null()); let chat = &*chat; - chat::dc_chat_get_name(chat) + chat.get_name() } #[no_mangle] @@ -1299,7 +1357,7 @@ pub unsafe extern "C" fn dc_chat_get_subtitle(chat: *mut dc_chat_t) -> *mut libc assert!(!chat.is_null()); let chat = &*chat; - chat::dc_chat_get_subtitle(chat) + chat.get_subtitle() } #[no_mangle] @@ -1307,7 +1365,7 @@ pub unsafe extern "C" fn dc_chat_get_profile_image(chat: *mut dc_chat_t) -> *mut assert!(!chat.is_null()); let chat = &*chat; - chat::dc_chat_get_profile_image(chat) + chat.get_profile_image() } #[no_mangle] @@ -1315,7 +1373,7 @@ pub unsafe extern "C" fn dc_chat_get_color(chat: *mut dc_chat_t) -> u32 { assert!(!chat.is_null()); let chat = &*chat; - chat::dc_chat_get_color(chat) + chat.get_color() } #[no_mangle] @@ -1323,7 +1381,7 @@ pub unsafe extern "C" fn dc_chat_get_archived(chat: *mut dc_chat_t) -> libc::c_i assert!(!chat.is_null()); let chat = &*chat; - chat::dc_chat_get_archived(chat) as libc::c_int + chat.is_archived() as libc::c_int } #[no_mangle] @@ -1331,7 +1389,7 @@ pub unsafe extern "C" fn dc_chat_is_unpromoted(chat: *mut dc_chat_t) -> libc::c_ assert!(!chat.is_null()); let chat = &*chat; - chat::dc_chat_is_unpromoted(chat) + chat.is_unpromoted() as libc::c_int } #[no_mangle] @@ -1339,7 +1397,7 @@ pub unsafe extern "C" fn dc_chat_is_self_talk(chat: *mut dc_chat_t) -> libc::c_i assert!(!chat.is_null()); let chat = &*chat; - chat::dc_chat_is_self_talk(chat) + chat.is_self_talk() as libc::c_int } #[no_mangle] @@ -1347,7 +1405,7 @@ pub unsafe extern "C" fn dc_chat_is_verified(chat: *mut dc_chat_t) -> libc::c_in assert!(!chat.is_null()); let chat = &*chat; - chat::dc_chat_is_verified(chat) + chat.is_verified() as libc::c_int } #[no_mangle] @@ -1355,7 +1413,7 @@ pub unsafe extern "C" fn dc_chat_is_sending_locations(chat: *mut dc_chat_t) -> l assert!(!chat.is_null()); let chat = &*chat; - chat::dc_chat_is_sending_locations(chat) as libc::c_int + chat.is_sending_locations() as libc::c_int } // dc_msg_t @@ -1836,3 +1894,23 @@ fn as_opt_str<'a>(s: *const libc::c_char) -> Option<&'a str> { Some(dc_tools::as_str(s)) } + +fn log_err(res: Result, context: &context::Context, msg: &str) { + if let Err(err) = res { + error!(context, 0, "{}: {}", msg, err); + } +} + +fn log_err_default( + res: Result, + context: &context::Context, + msg: &str, +) -> T { + match res { + Ok(t) => t, + Err(err) => { + error!(context, 0, "{}: {}", msg, err); + Default::default() + } + } +} diff --git a/examples/repl/cmdline.rs b/examples/repl/cmdline.rs index 066d13ba3..154aba350 100644 --- a/examples/repl/cmdline.rs +++ b/examples/repl/cmdline.rs @@ -1,7 +1,7 @@ use std::ffi::CString; use std::str::FromStr; -use deltachat::chat::*; +use deltachat::chat::{self, Chat}; use deltachat::chatlist::*; use deltachat::config; use deltachat::constants::*; @@ -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) { @@ -356,7 +356,7 @@ fn chat_prefix(chat: &Chat) -> &'static str { 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).ok() + Chat::load_from_db(context, chat_id).ok() } else { None }; @@ -606,23 +606,23 @@ 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, + chat.get_id(), as_str(temp_name), as_str(temp_subtitle), - dc_get_fresh_msg_cnt(context, dc_chat_get_id(&chat)) as libc::c_int, + 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, Some(&chat)); - let statestr = if dc_chat_get_archived(&chat) { + let statestr = if chat.is_archived() { " [Archived]" } else { match dc_lot_get_state(lot) { @@ -645,7 +645,7 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E to_string(text2), statestr, ×tr, - if dc_chat_is_sending_locations(&chat) { + if chat.is_sending_locations() { "📍" } else { "" @@ -672,25 +672,25 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E if !arg1.is_empty() { let chat_id = arg1.parse()?; println!("Selecting chat #{}", chat_id); - sel_chat = Some(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_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), + sel_chat.get_id(), as_str(temp_name), as_str(temp2), - if dc_chat_is_sending_locations(sel_chat) { + if sel_chat.is_sending_locations() { "📍" } else { "" @@ -702,65 +702,52 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E 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 = dc_create_chat_by_msg_id(context, msg_id_0 as uint32_t) as libc::c_int; - if chat_id_0 != 0 { - let chat = dc_get_chat(context, chat_id_0 as uint32_t)?; - println!("{}#{} created successfully.", chat_prefix(&chat), chat_id_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_c)?; + + 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_c)?; + + println!("VerifiedGroup#{} created successfully.", chat_id); } "addmember" => { 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.as_ref().unwrap()), + sel_chat.as_ref().unwrap().get_id(), contact_id_0 as uint32_t, ) { println!("Contact added to chat."); @@ -772,9 +759,9 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E 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( + if 0 != chat::remove_contact_from_chat( context, - dc_chat_get_id(sel_chat.as_ref().unwrap()), + sel_chat.as_ref().unwrap().get_id(), contact_id_1 as uint32_t, ) { println!("Contact added to chat."); @@ -785,7 +772,7 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E "groupname" => { 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.as_ref().unwrap()), arg1_c) { + if 0 != chat::set_chat_name(context, sel_chat.as_ref().unwrap().get_id(), arg1_c) { println!("Chat name set"); } else { bail!("Failed to set chat name"); @@ -795,9 +782,9 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E ensure!(sel_chat.is_some(), "No chat selected."); ensure!(!arg1.is_empty(), "Argument missing."); - if 0 != dc_set_chat_profile_image( + if 0 != chat::set_chat_profile_image( context, - dc_chat_get_id(sel_chat.as_ref().unwrap()), + sel_chat.as_ref().unwrap().get_id(), if !arg1.is_empty() { arg1_c } else { @@ -812,18 +799,14 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E "chatinfo" => { ensure!(sel_chat.is_some(), "No chat selected."); - let contacts = - dc_get_chat_contacts(context, dc_chat_get_id(sel_chat.as_ref().unwrap())); + 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.as_ref().unwrap()) - ), + dc_is_sending_locations_to_chat(context, sel_chat.as_ref().unwrap().get_id()), ); } "getlocations" => { @@ -832,7 +815,7 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E let contact_id = arg1.parse().unwrap_or_default(); let locations = dc_get_locations( context, - dc_chat_get_id(sel_chat.as_ref().unwrap()), + sel_chat.as_ref().unwrap().get_id(), contact_id, 0, 0, @@ -864,10 +847,10 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E ensure!(!arg1.is_empty(), "No timeout given."); let seconds = arg1.parse()?; - dc_send_locations_to_chat(context, dc_chat_get_id(sel_chat.as_ref().unwrap()), 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.", - dc_chat_get_id(sel_chat.as_ref().unwrap()), + sel_chat.as_ref().unwrap().get_id(), seconds ); } @@ -895,23 +878,11 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E let msg = format!("{} {}", arg1, arg2); - if 0 != dc_send_text_msg(context, dc_chat_get_id(sel_chat.as_ref().unwrap()), 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_some(), "No chat selected."); - if 0 != dc_send_text_msg( - context, - dc_chat_get_id(sel_chat.as_ref().unwrap()), - "".into(), - ) { - println!("Message sent."); - } else { - bail!("Sending failed."); - } + chat::send_text_msg(context, sel_chat.as_ref().unwrap().get_id(), "".into())?; } "sendimage" | "sendfile" => { ensure!(sel_chat.is_some(), "No chat selected."); @@ -927,14 +898,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.as_ref().unwrap()), 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 let Some(ref sel_chat) = sel_chat { - dc_chat_get_id(sel_chat) + sel_chat.get_id() } else { 0 as libc::c_uint }; @@ -953,13 +924,13 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E 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.as_ref().unwrap()), 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( + chat::set_draft( context, - dc_chat_get_id(sel_chat.as_ref().unwrap()), + sel_chat.as_ref().unwrap().get_id(), 0 as *mut dc_msg_t, ); println!("Draft deleted."); @@ -968,9 +939,9 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E "listmedia" => { 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.as_ref().unwrap()), + sel_chat.as_ref().unwrap().get_id(), Viewtype::Image, Viewtype::Gif, Viewtype::Video, @@ -991,12 +962,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."); @@ -1021,7 +996,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."); @@ -1091,8 +1066,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)); + let chat = Chat::load_from_db(context, chatlist.get_chat_id(i))?; + res += &format!("{}#{}", chat_prefix(&chat), chat.get_id()); } } diff --git a/examples/simple.rs b/examples/simple.rs index 261e4eb28..2a52d6041 100644 --- a/examples/simple.rs +++ b/examples/simple.rs @@ -5,7 +5,7 @@ use std::sync::{Arc, RwLock}; use std::{thread, time}; use tempfile::tempdir; -use deltachat::chat::*; +use deltachat::chat; use deltachat::chatlist::*; use deltachat::config; use deltachat::constants::Event; @@ -96,8 +96,8 @@ 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(); diff --git a/src/chat.rs b/src/chat.rs index 5e66f7016..eefcbe313 100644 --- a/src/chat.rs +++ b/src/chat.rs @@ -1,4 +1,5 @@ use std::ffi::CString; +use std::path::Path; use crate::chatlist::*; use crate::constants::*; @@ -17,7 +18,7 @@ use crate::x::*; use std::ptr; /// An object representing a single chat in memory. -/// Chat objects are created using eg. dc_get_chat() +/// 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)] @@ -35,662 +36,508 @@ pub struct Chat<'a> { } impl<'a> Chat<'a> { - pub fn new(context: &'a Context) -> Self { - Chat { - context, - id: 0, - typ: Chattype::Undefined, - name: std::ptr::null_mut(), - archived: false, - grpid: std::ptr::null_mut(), - blocked: Blocked::Not, - param: Params::new(), - gossiped_timestamp: 0, - is_sending_locations: false, - } - } -} -impl<'a> Drop for Chat<'a> { - fn drop(&mut self) { - unsafe { - free(self.name.cast()); - free(self.grpid.cast()); - } - } -} - -// handle chats -pub unsafe fn dc_create_chat_by_msg_id(context: &Context, msg_id: u32) -> u32 { - let mut chat_id: u32 = 0i32 as u32; - let mut send_event: libc::c_int = 0i32; - let msg = dc_msg_new_untyped(context); - - if dc_msg_load_from_db(msg, context, msg_id) { - if let Ok(chat) = dc_chat_load_from_db(context, (*msg).chat_id) { - if chat.id > 9 { - chat_id = chat.id; - if chat.blocked != Blocked::Not { - dc_unblock_chat(context, chat.id); - send_event = 1; - } - Contact::scaleup_origin_by_id(context, (*msg).from_id, Origin::CreateChat); - } - } - } - - dc_msg_unref(msg); - - if 0 != send_event { - context.call_cb(Event::MSGS_CHANGED, 0i32 as uintptr_t, 0i32 as uintptr_t); - } - chat_id -} - -pub unsafe fn dc_unblock_chat(context: &Context, chat_id: u32) { - 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(context: &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 mut c = Chat::new(context); - - c.id = row.get(0)?; - c.typ = 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(c) - }, - ); - - match res { - Err(err @ crate::error::Error::Sql(rusqlite::Error::QueryReturnedNoRows)) => Err(err), - Err(err) => match err { - _ => { - error!( + 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 mut c = Chat { context, - 0, "chat: failed to load from db {}: {:?}", chat_id, err - ); - Err(err) - } - }, - Ok(mut chat) => { - match chat.id { - 1 => unsafe { - free(chat.name.cast()); - chat.name = chat.context.stock_str(StockMessage::DeadDrop).strdup(); - }, - 6 => unsafe { - free(chat.name.cast()); - 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.cast()); - chat.name = chat.context.stock_str(StockMessage::StarredMsgs).strdup(); - }, - _ => { - unsafe { - if chat.typ == Chattype::Single { - free(chat.name.cast()); - let contacts = dc_get_chat_contacts(chat.context, chat.id); - let mut chat_name = "Err [Name not found]".to_owned(); + id: 0, + typ: Chattype::Undefined, + name: std::ptr::null_mut(), + archived: false, + grpid: std::ptr::null_mut(), + blocked: Blocked::Not, + param: Params::new(), + gossiped_timestamp: 0, + is_sending_locations: false, + }; - if !(*contacts).is_empty() { - if let Ok(contact) = Contact::get_by_id(chat.context, contacts[0]) { - chat_name = contact.get_display_name().to_owned(); - } - } + c.id = row.get(0)?; + c.typ = row.get(1)?; + c.name = unsafe { row.get::<_, String>(2)?.strdup() }; + c.grpid = unsafe { row.get::<_, String>(3)?.strdup() }; - chat.name = (&chat_name).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)?; - if chat.param.exists(Param::Selftalk) { - unsafe { - free(chat.name as *mut libc::c_void); - chat.name = chat.context.stock_str(StockMessage::SelfMsg).strdup(); - } - } - } - } - Ok(chat) - } - } -} - -pub unsafe fn dc_create_chat_by_contact_id(context: &Context, contact_id: u32) -> u32 { - let mut chat_id = 0; - let mut chat_blocked = Blocked::Not; - 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 chat_blocked != Blocked::Not { - 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, - Blocked::Not, - &mut chat_id, - None, - ); - 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: u32, - create_blocked: Blocked, - ret_chat_id: *mut u32, - mut ret_chat_blocked: Option<&mut Blocked>, -) { - let mut chat_id = 0; - let mut chat_blocked = Blocked::Not; - - if !ret_chat_id.is_null() { - *ret_chat_id = 0; - } - - if let Some(b) = ret_chat_blocked.as_mut() { - **b = Blocked::Not; - } - - 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 let Some(b) = ret_chat_blocked.as_mut() { - **b = chat_blocked; - } - return; - } - if let Ok(contact) = Contact::load_from_db(context, contact_id) { - let chat_name = contact.get_display_name(); - - match 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![], - ) { - 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(); + Ok(c) }, - Err(err) => panic!("{:?}", err), - } - } - - if !ret_chat_id.is_null() { - *ret_chat_id = chat_id - } - if let Some(b) = ret_chat_blocked.as_mut() { - **b = create_blocked; - } -} - -pub fn dc_lookup_real_nchat_by_contact_id( - context: &Context, - contact_id: u32, - ret_chat_id: *mut u32, - ret_chat_blocked: &mut Blocked, -) { - /* checks for "real" chats or self-chat */ - if !ret_chat_id.is_null() { - unsafe { *ret_chat_id = 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 }; - *ret_chat_blocked = blocked; - } -} - -pub unsafe fn dc_get_chat_id_by_contact_id(context: &Context, contact_id: u32) -> u32 { - let mut chat_id: u32 = 0i32 as u32; - let mut chat_id_blocked = Blocked::Not; - dc_lookup_real_nchat_by_contact_id(context, contact_id, &mut chat_id, &mut chat_id_blocked); - if chat_id_blocked != Blocked::Not { - 0 - } else { - chat_id - } -} - -pub unsafe fn dc_prepare_msg<'a>( - context: &'a Context, - chat_id: u32, - mut msg: *mut dc_msg_t<'a>, -) -> u32 { - if msg.is_null() || chat_id <= 9i32 as libc::c_uint { - return 0i32 as u32; - } - (*msg).state = DC_STATE_OUT_PREPARING; - let msg_id: u32 = 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: u32, - mut msg: *mut dc_msg_t<'a>, -) -> u32 { - let mut OK_TO_CONTINUE = true; - (*msg).id = 0i32 as u32; - (*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); - if let Ok(mut chat) = dc_chat_load_from_db(context, chat_id) { - if (*msg).state != DC_STATE_OUT_PREPARING { - (*msg).state = DC_STATE_OUT_PENDING - } - (*msg).id = prepare_msg_raw( - context, - &mut chat, - msg, - dc_create_smeared_timestamp(context), - ); - (*msg).chat_id = chat_id - } - } - (*msg).id -} - -#[allow(non_snake_case)] -unsafe fn prepare_msg_raw( - context: &Context, - chat: &mut Chat, - msg: *mut dc_msg_t, - timestamp: i64, -) -> u32 { - 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.typ == Chattype::Single - || chat.typ == Chattype::Group - || chat.typ == Chattype::VerifiedGroup) - { - error!(context, 0, "Cannot send to chat type #{}.", chat.typ,); - } else if (chat.typ == Chattype::Group || chat.typ == Chattype::VerifiedGroup) - && 0 == dc_is_contact_in_chat(context, chat.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 chat.typ == Chattype::Group || chat.typ == Chattype::VerifiedGroup { - chat.grpid - } else { - ptr::null_mut() - }, - from_c.as_ptr(), - ); - - if chat.typ == Chattype::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 { + match res { + Err(err @ crate::error::Error::Sql(rusqlite::Error::QueryReturnedNoRows)) => Err(err), + Err(err) => match err { + _ => { error!( context, - 0, "Cannot send message, contact for chat #{} not found.", chat.id, + 0, "chat: failed to load from db {}: {:?}", chat_id, err ); - OK_TO_CONTINUE = false; + Err(err) } + }, + Ok(mut chat) => { + match chat.id { + 1 => unsafe { + free(chat.name.cast()); + chat.name = chat.context.stock_str(StockMessage::DeadDrop).strdup(); + }, + 6 => unsafe { + free(chat.name.cast()); + 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.cast()); + chat.name = chat.context.stock_str(StockMessage::StarredMsgs).strdup(); + }, + _ => { + unsafe { + if chat.typ == Chattype::Single { + free(chat.name.cast()); + 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).strdup(); + } + } + + if chat.param.exists(Param::Selftalk) { + unsafe { + free(chat.name as *mut libc::c_void); + chat.name = chat.context.stock_str(StockMessage::SelfMsg).strdup(); + } + } + } + } + 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 unsafe fn get_id(&self) -> u32 { + self.id + } + + pub unsafe fn get_type(&self) -> Chattype { + self.typ + } + + pub unsafe fn get_name(&self) -> *mut libc::c_char { + dc_strdup(self.name) + } + + pub unsafe fn get_subtitle(&self) -> *mut libc::c_char { + /* returns either the address or the number of chat members */ + + let mut ret: *mut libc::c_char = std::ptr::null_mut(); + if self.typ == Chattype::Single && self.param.exists(Param::Selftalk) { + ret = self + .context + .stock_str(StockMessage::SelfTalkSubTitle) + .strdup(); + } else if self.typ == Chattype::Single { + let ret_raw: String = 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()); + ret = ret_raw.strdup(); + } else if self.typ == Chattype::Group || self.typ == Chattype::VerifiedGroup { + if self.id == 1 { + ret = self.context.stock_str(StockMessage::DeadDrop).strdup(); } else { - if chat.typ == Chattype::Group || chat.typ == Chattype::VerifiedGroup { - if chat.param.get_int(Param::Unpromoted).unwrap_or_default() == 1 { - chat.param.remove(Param::Unpromoted); - dc_chat_update_param(chat); + let cnt = get_chat_contact_cnt(self.context, self.id); + ret = self + .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) + } + } + 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) -> *mut libc::c_char { + let mut image_abs: *mut libc::c_char = 0 as *mut libc::c_char; + + if let Some(image_rel) = self.param.get(Param::ProfileImage) { + if !image_rel.is_empty() { + image_abs = 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]) { + if let Some(img) = contact.get_profile_image() { + image_abs = img.strdup(); } } } - 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)?; + image_abs + } - 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" - }, - ); + pub fn get_color(&self) -> u32 { + let mut color: u32 = 0i32 as u32; + + 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 = unsafe { dc_str_to_color(self.name) } as u32 + } + + 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 + } 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; } - } 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); } - 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) } } - 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 - // 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( + if (*msg).param.exists(Param::SetLatitude) { + if sql::execute( context, &context.sql, - "locations", - "timestamp", - timestamp, - "from_id", - DC_CONTACT_ID_SELF as i32, - ); + "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 + // add message to the database - if sql::execute( + 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, + self.id as i32, 1i32, to_id as i32, timestamp, @@ -716,83 +563,298 @@ unsafe fn prepare_msg_raw( context, 0, "Cannot send message, cannot insert to database (chat #{}).", - chat.id, + 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 > 9 { + 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, + ); } } } - 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); + unsafe { dc_msg_unref(msg) }; - msg_id -} - -// TODO should return bool /rtn -unsafe fn get_parent_mime_headers( - chat: &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 !(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; - } + if send_event { + context.call_cb(Event::MSGS_CHANGED, 0, 0); } - success + + ensure!(chat_id > 0, "failed to load create chat"); + + Ok(chat_id) } -pub unsafe fn dc_chat_is_self_talk(chat: &Chat) -> libc::c_int { - chat.param.exists(Param::Selftalk) as libc::c_int +/// 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) } -/******************************************************************************* - * Sending messages - ******************************************************************************/ -// TODO should return bool /rtn -unsafe fn last_msg_in_chat_encrypted(context: &Context, sql: &Sql, chat_id: u32) -> libc::c_int { +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 \ @@ -804,33 +866,18 @@ unsafe fn last_msg_in_chat_encrypted(context: &Context, sql: &Sql, chat_id: u32) if let Some(ref packed) = packed { match packed.parse::() { - Ok(param) => param.exists(Param::GuranteeE2ee) as libc::c_int, + Ok(param) => param.exists(Param::GuranteeE2ee), Err(err) => { error!(context, 0, "invalid params stored: '{}', {:?}", packed, err); - 0 + false } } } else { - 0 + false } } -// 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: u32, - contact_id: u32, -) -> libc::c_int { +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) */ @@ -844,33 +891,45 @@ pub unsafe fn dc_is_contact_in_chat( } // Should return Result -pub fn dc_unarchive_chat(context: &Context, chat_id: u32) { +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], ) - .ok(); } -pub unsafe fn dc_send_msg<'a>(context: &'a Context, chat_id: u32, msg: *mut dc_msg_t<'a>) -> u32 { - if msg.is_null() { - return 0; - } +/// 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 { - if 0 == prepare_msg_common(context, chat_id, msg) { - return 0; - } + // automatically prepare normal messages + prepare_msg_common(context, chat_id, msg)?; } else { - if chat_id != 0 && chat_id != (*msg).chat_id { - return 0; - } + // 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); } - if 0 == dc_job_send_msg(context, (*msg).id) { - return 0; - } + + 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, @@ -892,7 +951,8 @@ pub unsafe fn dc_send_msg<'a>(context: &'a Context, chat_id: u32, msg: *mut dc_m } else { let copy = dc_get_msg(context, id as u32); if !copy.is_null() { - dc_send_msg(context, 0, copy); + // TODO: handle cleanup and return early instead + send_msg(context, 0, copy).unwrap(); } dc_msg_unref(copy); } @@ -902,67 +962,61 @@ pub unsafe fn dc_send_msg<'a>(context: &'a Context, chat_id: u32, msg: *mut dc_m } } - (*msg).id + Ok((*msg).id) } -pub unsafe fn dc_send_text_msg(context: &Context, chat_id: u32, text_to_send: String) -> u32 { - if chat_id <= 9 { - warn!( - context, - 0, "dc_send_text_msg: bad chat_id = {} <= 9", chat_id - ); - return 0; - } +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); - let ret = dc_send_msg(context, chat_id, msg); - dc_msg_unref(msg); - ret + send_msg(context, chat_id, msg) } -pub unsafe fn dc_set_draft(context: &Context, chat_id: u32, msg: *mut dc_msg_t) { - if chat_id <= 9i32 as libc::c_uint { +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 0 != set_draft_raw(context, chat_id, msg) { + if 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: u32, msg: *mut dc_msg_t) -> libc::c_int { +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: libc::c_int = 0i32; + 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 = 1i32 + 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) { - 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)); + 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); + } } - free(pathNfilename as *mut _); } else { OK_TO_CONTINUE = false; } @@ -985,7 +1039,7 @@ unsafe fn set_draft_raw(context: &Context, chat_id: u32, msg: *mut dc_msg_t) -> ) .is_ok() { - sth_changed = 1; + sth_changed = true; } } } @@ -994,30 +1048,26 @@ unsafe fn set_draft_raw(context: &Context, chat_id: u32, msg: *mut dc_msg_t) -> } fn get_draft_msg_id(context: &Context, chat_id: u32) -> u32 { - let draft_msg_id: i32 = context + context .sql - .query_row_col( + .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(); - - draft_msg_id as u32 + .unwrap_or_default() as u32 } -pub unsafe fn dc_get_draft(context: &Context, chat_id: u32) -> *mut dc_msg_t { - let draft_msg_id: u32; - 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 { +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(); } - draft_msg = dc_msg_new_untyped(context); + 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(); @@ -1026,7 +1076,7 @@ pub unsafe fn dc_get_draft(context: &Context, chat_id: u32) -> *mut dc_msg_t { draft_msg } -pub fn dc_get_chat_msgs( +pub fn get_chat_msgs( context: &Context, chat_id: u32, flags: u32, @@ -1106,22 +1156,22 @@ pub fn dc_get_chat_msgs( } } -pub fn dc_get_msg_cnt(context: &Context, chat_id: u32) -> libc::c_int { +pub fn get_msg_cnt(context: &Context, chat_id: u32) -> usize { context .sql - .query_row_col( + .query_row_col::<_, i32>( context, "SELECT COUNT(*) FROM msgs WHERE chat_id=?;", params![chat_id as i32], 0, ) - .unwrap_or_default() + .unwrap_or_default() as usize } -pub fn dc_get_fresh_msg_cnt(context: &Context, chat_id: u32) -> libc::c_int { +pub fn get_fresh_msg_cnt(context: &Context, chat_id: u32) -> usize { context .sql - .query_row_col( + .query_row_col::<_, i32>( context, "SELECT COUNT(*) FROM msgs \ WHERE state=10 \ @@ -1130,66 +1180,53 @@ pub fn dc_get_fresh_msg_cnt(context: &Context, chat_id: u32) -> libc::c_int { params![chat_id as i32], 0, ) - .unwrap_or_default() + .unwrap_or_default() as usize } -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; +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(()); } - if sql::execute( + + 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 + + Ok(()) } -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; +pub fn marknoticed_all_chats(context: &Context) -> Result<(), Error> { + if !context.sql.exists( + "SELECT id FROM msgs \ + WHERE state=10;", + params![], + )? { + return Ok(()); } - if sql::execute( + 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 + Ok(()) } -pub fn dc_get_chat_media( +pub fn get_chat_media( context: &Context, chat_id: u32, msg_type: Viewtype, @@ -1222,7 +1259,7 @@ pub fn dc_get_chat_media( ).unwrap_or_else(|_| std::ptr::null_mut()) } -pub unsafe fn dc_get_next_media( +pub unsafe fn get_next_media( context: &Context, curr_msg_id: u32, dir: libc::c_int, @@ -1237,7 +1274,7 @@ pub unsafe fn dc_get_next_media( let cnt: libc::c_int; if dc_msg_load_from_db(msg, context, curr_msg_id) { - list = dc_get_chat_media( + list = get_chat_media( context, (*msg).chat_id, if msg_type != Viewtype::Unknown { @@ -1277,97 +1314,80 @@ pub unsafe fn dc_get_next_media( 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( +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], - ) - .is_err() - { - return false; - } + )?; } - if sql::execute( + + 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 + Ok(()) } -pub fn dc_delete_chat(context: &Context, chat_id: u32) -> bool { +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. */ - if chat_id <= 9 { - return false; - } - if dc_chat_load_from_db(context, chat_id).is_err() { - return false; - } - - if sql::execute( + 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], - ) - .is_err() - { - return false; - } - if sql::execute( + )?; + + sql::execute( context, &context.sql, "DELETE FROM msgs WHERE chat_id=?;", params![chat_id as i32], - ) - .is_err() - { - return false; - } - if sql::execute( + )?; + + sql::execute( context, &context.sql, "DELETE FROM chats_contacts WHERE chat_id=?;", params![chat_id as i32], - ) - .is_err() - { - return false; - } - if sql::execute( + )?; + + 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 + Ok(()) } -pub fn dc_get_chat_contacts(context: &Context, chat_id: u32) -> Vec { +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) */ @@ -1391,62 +1411,56 @@ pub fn dc_get_chat_contacts(context: &Context, chat_id: u32) -> Vec { .unwrap_or_default() } -pub fn dc_get_chat(context: &Context, chat_id: u32) -> Result { - dc_chat_load_from_db(context, chat_id) -} - -// handle group chats -pub unsafe fn dc_create_group_chat( +pub unsafe fn create_group_chat( context: &Context, - verified: libc::c_int, + verified: VerifiedStatus, chat_name: *const libc::c_char, -) -> u32 { - let mut chat_id = 0; +) -> Result { + ensure!( + !chat_name.is_null() && *chat_name.offset(0) as libc::c_int != 0, + "Invalid chat name" + ); - 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( + + sql::execute( context, &context.sql, "INSERT INTO chats (type, name, grpid, param) VALUES(?, ?, ?, \'U=1\');", params![ - if verified != 0 { 130 } else { 120 }, + if verified != VerifiedStatus::Unverified { + Chattype::VerifiedGroup + } else { + Chattype::Group + }, 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); - } + )?; + + 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); } - } - if 0 != chat_id { + context.call_cb(Event::MSGS_CHANGED, 0 as uintptr_t, 0 as uintptr_t); } - chat_id + 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 dc_add_to_chat_contacts_table( - context: &Context, - chat_id: u32, - contact_id: u32, -) -> libc::c_int { +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( @@ -1458,17 +1472,13 @@ pub fn dc_add_to_chat_contacts_table( .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) +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 dc_add_contact_to_chat_ex( +pub unsafe fn add_contact_to_chat_ex( context: &Context, chat_id: u32, contact_id: u32, @@ -1483,11 +1493,11 @@ pub unsafe fn dc_add_contact_to_chat_ex( } let mut msg = dc_msg_new_untyped(context); - dc_reset_gossiped_timestamp(context, chat_id); + 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 = dc_chat_load_from_db(context, chat_id); + let chat = Chat::load_from_db(context, chat_id); if !(0 == real_group_exists(context, chat_id) || !Contact::real_exists_by_id(context, contact_id) @@ -1496,7 +1506,7 @@ pub unsafe fn dc_add_contact_to_chat_ex( { let mut chat = chat.unwrap(); - if !(dc_is_contact_in_chat(context, chat_id, 1 as u32) == 1) { + if !(is_contact_in_chat(context, chat_id, 1 as u32) == 1) { log_event!( context, Event::ERROR_SELF_NOT_IN_GROUP, @@ -1507,7 +1517,7 @@ pub unsafe fn dc_add_contact_to_chat_ex( /* 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(&mut chat); + chat.update_param().unwrap(); } let self_addr = context .sql @@ -1517,7 +1527,7 @@ pub unsafe fn dc_add_contact_to_chat_ex( // 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 != is_contact_in_chat(context, chat_id, contact_id) { if 0 == flags & 0x1 { success = 1; OK_TO_CONTINUE = false; @@ -1534,7 +1544,7 @@ pub unsafe fn dc_add_contact_to_chat_ex( } } if OK_TO_CONTINUE { - if 0 == dc_add_to_chat_contacts_table(context, chat_id, contact_id) { + if 0 == add_to_chat_contacts_table(context, chat_id, contact_id) { OK_TO_CONTINUE = false; } } @@ -1551,7 +1561,7 @@ pub unsafe fn dc_add_contact_to_chat_ex( (*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); + (*msg).id = send_msg(context, chat_id, msg).unwrap_or_default(); context.call_cb( Event::MSGS_CHANGED, chat_id as uintptr_t, @@ -1586,12 +1596,12 @@ fn real_group_exists(context: &Context, chat_id: u32) -> libc::c_int { .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); +pub fn reset_gossiped_timestamp(context: &Context, chat_id: u32) { + set_gossiped_timestamp(context, chat_id, 0); } // Should return Result -pub fn dc_set_gossiped_timestamp(context: &Context, chat_id: u32, timestamp: i64) { +pub fn set_gossiped_timestamp(context: &Context, chat_id: u32, timestamp: i64) { if 0 != chat_id { info!( context, @@ -1621,7 +1631,7 @@ pub fn dc_set_gossiped_timestamp(context: &Context, chat_id: u32, timestamp: i64 } // TODO should return bool /rtn -pub unsafe fn dc_remove_contact_from_chat( +pub unsafe fn remove_contact_from_chat( context: &Context, chat_id: u32, contact_id: u32, @@ -1636,11 +1646,11 @@ pub unsafe fn dc_remove_contact_from_chat( /* 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 = dc_chat_load_from_db(context, chat_id); + let chat = Chat::load_from_db(context, chat_id); if !(0 == real_group_exists(context, chat_id) || chat.is_err()) { let chat = chat.unwrap(); - if !(dc_is_contact_in_chat(context, chat_id, 1 as u32) == 1) { + if !(is_contact_in_chat(context, chat_id, 1 as u32) == 1) { log_event!( context, Event::ERROR_SELF_NOT_IN_GROUP, @@ -1653,7 +1663,7 @@ pub unsafe fn dc_remove_contact_from_chat( 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); + set_group_explicitly_left(context, chat.grpid); (*msg).text = Some(context.stock_system_msg( StockMessage::MsgGroupLeft, "", @@ -1670,7 +1680,7 @@ pub unsafe fn dc_remove_contact_from_chat( } (*msg).param.set_int(Param::Cmd, 5); (*msg).param.set(Param::Arg, contact.get_addr()); - (*msg).id = dc_send_msg(context, chat_id, msg); + (*msg).id = send_msg(context, chat_id, msg).unwrap_or_default(); context.call_cb( Event::MSGS_CHANGED, chat_id as uintptr_t, @@ -1698,8 +1708,8 @@ pub unsafe fn dc_remove_contact_from_chat( } // 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) { +fn set_group_explicitly_left(context: &Context, grpid: *const libc::c_char) { + if 0 == is_group_explicitly_left(context, grpid) { sql::execute( context, &context.sql, @@ -1711,7 +1721,7 @@ fn dc_set_group_explicitly_left(context: &Context, grpid: *const libc::c_char) { } // TODO should return bool /rtn -pub fn dc_is_group_explicitly_left(context: &Context, grpid: *const libc::c_char) -> libc::c_int { +pub fn is_group_explicitly_left(context: &Context, grpid: *const libc::c_char) -> libc::c_int { context .sql .exists( @@ -1722,7 +1732,7 @@ pub fn dc_is_group_explicitly_left(context: &Context, grpid: *const libc::c_char } // TODO should return bool /rtn -pub unsafe fn dc_set_chat_name( +pub unsafe fn set_chat_name( context: &Context, chat_id: u32, new_name: *const libc::c_char, @@ -1735,13 +1745,13 @@ pub unsafe fn dc_set_chat_name( return 0; } - let chat = dc_chat_load_from_db(context, chat_id); + let chat = Chat::load_from_db(context, chat_id); if !(0i32 == real_group_exists(context, chat_id) || chat.is_err()) { let chat = chat.unwrap(); if strcmp(chat.name, new_name) == 0i32 { success = 1i32 - } else if !(dc_is_contact_in_chat(context, chat_id, 1i32 as u32) == 1i32) { + } else if !(is_contact_in_chat(context, chat_id, 1i32 as u32) == 1i32) { log_event!( context, Event::ERROR_SELF_NOT_IN_GROUP, @@ -1774,7 +1784,7 @@ pub unsafe fn dc_set_chat_name( if !chat.name.is_null() { (*msg).param.set(Param::Arg, as_str(chat.name)); } - (*msg).id = dc_send_msg(context, chat_id, msg); + (*msg).id = send_msg(context, chat_id, msg).unwrap_or_default(); context.call_cb( Event::MSGS_CHANGED, chat_id as uintptr_t, @@ -1798,7 +1808,7 @@ pub unsafe fn dc_set_chat_name( // TODO should return bool /rtn #[allow(non_snake_case)] -pub unsafe fn dc_set_chat_profile_image( +pub unsafe fn set_chat_profile_image( context: &Context, chat_id: u32, new_image: *const libc::c_char, @@ -1811,12 +1821,12 @@ pub unsafe fn dc_set_chat_profile_image( } let mut msg = 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) { - let chat = dc_chat_load_from_db(context, chat_id); + let mut new_image_rel = None; + let chat = Chat::load_from_db(context, chat_id); if !(0i32 == real_group_exists(context, chat_id) || chat.is_err()) { let mut chat = chat.unwrap(); - if !(dc_is_contact_in_chat(context, chat_id, 1i32 as u32) == 1i32) { + if !(is_contact_in_chat(context, chat_id, 1i32 as u32) == 1i32) { log_event!( context, Event::ERROR_SELF_NOT_IN_GROUP, @@ -1826,23 +1836,28 @@ pub unsafe fn dc_set_chat_profile_image( } 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) { + let mut img = to_string(new_image); + 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 { - chat.param.set(Param::ProfileImage, as_str(new_image_rel)); - if !(0 == dc_chat_update_param(&mut chat)) { + 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); - (*msg).param.set(Param::Arg, as_str(new_image_rel)); + 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_null() { + if new_image_rel.is_some() { StockMessage::MsgGrpImgChanged } else { StockMessage::MsgGrpImgDeleted @@ -1851,7 +1866,7 @@ pub unsafe fn dc_set_chat_profile_image( "", DC_CONTACT_ID_SELF as u32, )); - (*msg).id = dc_send_msg(context, chat_id, msg); + (*msg).id = send_msg(context, chat_id, msg).unwrap_or_default(); context.call_cb( Event::MSGS_CHANGED, chat_id as uintptr_t, @@ -1870,12 +1885,11 @@ pub unsafe fn dc_set_chat_profile_image( } dc_msg_unref(msg); - free(new_image_rel as *mut libc::c_void); success } -pub unsafe fn dc_forward_msgs( +pub unsafe fn forward_msgs( context: &Context, msg_ids: *const u32, msg_cnt: libc::c_int, @@ -1889,8 +1903,8 @@ pub unsafe fn dc_forward_msgs( let mut created_db_entries = Vec::new(); let mut curr_timestamp: i64; - dc_unarchive_chat(context, chat_id); - if let Ok(mut chat) = dc_chat_load_from_db(context, chat_id) { + 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() @@ -1930,7 +1944,9 @@ pub unsafe fn dc_forward_msgs( if (*msg).state == DC_STATE_OUT_PREPARING { let fresh9 = curr_timestamp; curr_timestamp = curr_timestamp + 1; - new_msg_id = prepare_msg_raw(context, &mut chat, msg, fresh9); + 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; @@ -1950,7 +1966,9 @@ pub unsafe fn dc_forward_msgs( (*msg).state = DC_STATE_OUT_PENDING; let fresh10 = curr_timestamp; curr_timestamp = curr_timestamp + 1; - new_msg_id = prepare_msg_raw(context, &mut chat, msg, fresh10); + 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); @@ -1968,60 +1986,7 @@ pub unsafe fn dc_forward_msgs( dc_msg_unref(msg); } -pub unsafe fn dc_chat_get_id(chat: &Chat) -> u32 { - chat.id -} - -pub unsafe fn dc_chat_get_type(chat: &Chat) -> Chattype { - chat.typ -} - -pub unsafe fn dc_chat_get_name(chat: &Chat) -> *mut libc::c_char { - dc_strdup(chat.name) -} - -pub unsafe fn dc_chat_get_subtitle(chat: &Chat) -> *mut libc::c_char { - /* returns either the address or the number of chat members */ - - let mut ret: *mut libc::c_char = std::ptr::null_mut(); - if chat.typ == Chattype::Single && chat.param.exists(Param::Selftalk) { - ret = chat - .context - .stock_str(StockMessage::SelfTalkSubTitle) - .strdup(); - } else if chat.typ == Chattype::Single { - 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.typ == Chattype::Group || chat.typ == Chattype::VerifiedGroup { - 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 { +pub fn get_chat_contact_cnt(context: &Context, chat_id: u32) -> libc::c_int { context .sql .query_row_col( @@ -2033,70 +1998,7 @@ pub fn dc_get_chat_contact_cnt(context: &Context, chat_id: u32) -> libc::c_int { .unwrap_or_default() } -pub unsafe fn dc_chat_get_profile_image(chat: &Chat) -> *mut libc::c_char { - let mut image_abs: *mut libc::c_char = 0 as *mut libc::c_char; - - let 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.typ == Chattype::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: &Chat) -> u32 { - let mut color: u32 = 0i32 as u32; - - if chat.typ == Chattype::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 u32 - } - - color -} - -// TODO should return bool /rtn -pub unsafe fn dc_chat_get_archived(chat: &Chat) -> bool { - chat.archived -} - -// TODO should return bool /rtn -pub unsafe fn dc_chat_is_unpromoted(chat: &Chat) -> libc::c_int { - 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: &Chat) -> libc::c_int { - (chat.typ == Chattype::VerifiedGroup) as libc::c_int -} - -// TODO should return bool /rtn -pub unsafe fn dc_chat_is_sending_locations(chat: &Chat) -> bool { - chat.is_sending_locations -} - -pub fn dc_get_chat_cnt(context: &Context) -> usize { +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 @@ -2113,7 +2015,7 @@ pub fn dc_get_chat_cnt(context: &Context) -> usize { } } -pub unsafe fn dc_get_chat_id_by_grpid( +pub unsafe fn get_chat_id_by_grpid( context: &Context, grpid: *const libc::c_char, ret_blocked: Option<&mut Blocked>, @@ -2144,7 +2046,7 @@ pub unsafe fn dc_get_chat_id_by_grpid( .unwrap_or_default() } -pub fn dc_add_device_msg(context: &Context, chat_id: u32, text: *const libc::c_char) { +pub fn add_device_msg(context: &Context, chat_id: u32, text: *const libc::c_char) { if text.is_null() { return; } @@ -2161,7 +2063,7 @@ pub fn dc_add_device_msg(context: &Context, chat_id: u32, text: *const libc::c_c chat_id as i32, 2, 2, - unsafe {dc_create_smeared_timestamp(context)}, + dc_create_smeared_timestamp(context), Viewtype::Text, DC_STATE_IN_NOTICED, as_str(text), @@ -2186,3 +2088,12 @@ pub fn dc_add_device_msg(context: &Context, chat_id: u32, text: *const libc::c_c msg_id as uintptr_t, ); } + +impl<'a> Drop for Chat<'a> { + fn drop(&mut self) { + unsafe { + free(self.name.cast()); + free(self.grpid.cast()); + } + } +} diff --git a/src/chatlist.rs b/src/chatlist.rs index b8e6ac2a9..4fc1c308c 100644 --- a/src/chatlist.rs +++ b/src/chatlist.rs @@ -265,7 +265,7 @@ impl<'a> Chatlist<'a> { let chat = if let Some(chat) = chat { chat } else { - if let Ok(chat) = dc_get_chat(self.context, self.ids[index].0) { + if let Ok(chat) = Chat::load_from_db(self.context, self.ids[index].0) { chat_loaded = chat; &chat_loaded } else { diff --git a/src/contact.rs b/src/contact.rs index 62024bcd2..d205baace 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. diff --git a/src/context.rs b/src/context.rs index 8f2b8e87b..269046201 100644 --- a/src/context.rs +++ b/src/context.rs @@ -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_imex.rs b/src/dc_imex.rs index aff4dec8d..f15cf2ec9 100644 --- a/src/dc_imex.rs +++ b/src/dc_imex.rs @@ -6,7 +6,7 @@ use mmime::mmapstring::*; use mmime::other::*; use rand::{thread_rng, Rng}; -use crate::chat::*; +use crate::chat; use crate::config::Config; use crate::constants::*; use crate::context::Context; @@ -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 2a26142e0..fd17c285e 100644 --- a/src/dc_job.rs +++ b/src/dc_job.rs @@ -6,7 +6,7 @@ use std::time::Duration; use rand::{thread_rng, Rng}; -use crate::chat::*; +use crate::chat; use crate::constants::*; use crate::context::Context; use crate::dc_configure::*; @@ -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 bb3804b82..9dd402960 100644 --- a/src/dc_location.rs +++ b/src/dc_location.rs @@ -3,7 +3,7 @@ use std::ffi::CString; use quick_xml; use quick_xml::events::{BytesEnd, BytesStart, BytesText}; -use crate::chat::*; +use crate::chat; use crate::constants::Event; use crate::constants::*; use crate::context::*; @@ -104,7 +104,7 @@ 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, @@ -113,7 +113,7 @@ pub unsafe fn dc_send_locations_to_chat( 0, )) .unwrap(); - dc_add_device_msg(context, chat_id, stock_str.as_ptr()); + chat::add_device_msg(context, chat_id, stock_str.as_ptr()); } context.call_cb( Event::CHAT_MODIFIED, @@ -697,7 +697,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(()) @@ -736,7 +737,7 @@ pub unsafe fn dc_job_do_DC_JOB_MAYBE_SEND_LOC_ENDED(context: &Context, job: &mut 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()); + chat::add_device_msg(context, chat_id, stock_str.as_ptr()); context.call_cb( Event::CHAT_MODIFIED, chat_id as usize, diff --git a/src/dc_lot.rs b/src/dc_lot.rs index b740804a9..134a6bcf6 100644 --- a/src/dc_lot.rs +++ b/src/dc_lot.rs @@ -135,7 +135,7 @@ 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 { diff --git a/src/dc_mimefactory.rs b/src/dc_mimefactory.rs index 013eed860..6300e87cb 100644 --- a/src/dc_mimefactory.rs +++ b/src/dc_mimefactory.rs @@ -10,7 +10,7 @@ use mmime::mailmime_write_mem::*; use mmime::mmapstring::*; use mmime::other::*; -use crate::chat::*; +use crate::chat::{self, Chat}; use crate::constants::*; use crate::contact::*; use crate::context::{dc_get_version_str, Context}; @@ -112,14 +112,14 @@ pub unsafe fn dc_mimefactory_load_msg( (*factory).msg = dc_msg_new_untyped(context); if dc_msg_load_from_db((*factory).msg, context, msg_id) { - if let Ok(chat) = dc_chat_load_from_db(context, (*(*factory).msg).chat_id) { + if let Ok(chat) = Chat::load_from_db(context, (*(*factory).msg).chat_id) { (*factory).chat = Some(chat); let chat = (*factory).chat.as_ref().unwrap(); load_from(factory); (*factory).req_mdn = 0; - if 0 != dc_chat_is_self_talk(chat) { + if chat.is_self_talk() { clist_insert_after( (*factory).recipients_names, (*(*factory).recipients_names).last, @@ -830,7 +830,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 @@ -1146,22 +1146,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); @@ -1175,7 +1173,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 @@ -1199,7 +1197,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() { @@ -1292,17 +1290,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 c263ee82d..20713f170 100644 --- a/src/dc_msg.rs +++ b/src/dc_msg.rs @@ -1,7 +1,7 @@ use std::ffi::CString; use std::path::Path; -use crate::chat::*; +use crate::chat::{self, Chat}; use crate::constants::*; use crate::contact::*; use crate::context::*; @@ -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() { @@ -757,7 +755,7 @@ pub unsafe fn dc_msg_get_summary<'a>( let chat = if let Some(chat) = chat { chat } else { - if let Ok(chat) = dc_get_chat((*msg).context, (*msg).chat_id) { + if let Ok(chat) = Chat::load_from_db((*msg).context, (*msg).chat_id) { chat_loaded = chat; &chat_loaded } else { @@ -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, "failed to create chat"); + 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 b5d46e8cc..dbcff7a2e 100644 --- a/src/dc_qr.rs +++ b/src/dc_qr.rs @@ -1,6 +1,6 @@ use percent_encoding::percent_decode_str; -use crate::chat::*; +use crate::chat; use crate::constants::Blocked; use crate::contact::*; use crate::context::Context; @@ -231,13 +231,13 @@ 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, Blocked::Deaddrop, - &mut chat_id, - None, - ); + ) + .unwrap_or_default(); + chat_id = id; device_msg = dc_mprintf( b"%s verified.\x00" as *const u8 as *const libc::c_char, peerstate.addr, @@ -288,7 +288,7 @@ pub unsafe fn dc_check_qr(context: &Context, qr: *const libc::c_char) -> *mut dc (*qr_parsed).text1 = dc_strdup(qr) } if !device_msg.is_null() { - dc_add_device_msg(context, chat_id, device_msg); + chat::add_device_msg(context, chat_id, device_msg); } } } diff --git a/src/dc_receive_imf.rs b/src/dc_receive_imf.rs index de26cef40..461cf9bf7 100644 --- a/src/dc_receive_imf.rs +++ b/src/dc_receive_imf.rs @@ -10,7 +10,7 @@ use mmime::mmapstring::*; use mmime::other::*; use sha2::{Digest, Sha256}; -use crate::chat::*; +use crate::chat::{self, Chat}; use crate::constants::*; use crate::contact::*; use crate::context::Context; @@ -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 = Blocked::Not; - 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 @@ -444,7 +438,7 @@ unsafe fn add_parts( &mut chat_id_blocked, ); if 0 != *chat_id && Blocked::Not != chat_id_blocked && create_blocked == Blocked::Not { - dc_unblock_chat(context, *chat_id); + chat::unblock(context, *chat_id); chat_id_blocked = Blocked::Not; } } @@ -472,17 +466,15 @@ unsafe fn add_parts( *chat_id = test_normal_chat_id; 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, - Some(&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 && Blocked::Not != chat_id_blocked { if Blocked::Not == create_blocked { - dc_unblock_chat(context, *chat_id); + 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, @@ -532,7 +524,7 @@ unsafe fn add_parts( &mut chat_id_blocked, ); if 0 != *chat_id && Blocked::Not != chat_id_blocked { - dc_unblock_chat(context, *chat_id); + chat::unblock(context, *chat_id); chat_id_blocked = Blocked::Not; } } @@ -542,18 +534,17 @@ unsafe fn add_parts( } else { Blocked::Deaddrop }; - dc_create_or_lookup_nchat_by_contact_id( - context, - *to_id, - create_blocked, - chat_id, - Some(&mut chat_id_blocked), - ); + 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 { - dc_unblock_chat(context, *chat_id); + chat::unblock(context, *chat_id); chat_id_blocked = Blocked::Not; } } @@ -562,15 +553,13 @@ 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, - Blocked::Not, - chat_id, - Some(&mut chat_id_blocked), - ); + 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 { - dc_unblock_chat(context, *chat_id); + chat::unblock(context, *chat_id); chat_id_blocked = Blocked::Not; } } @@ -593,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) @@ -1236,7 +1225,7 @@ 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( + chat_id = chat::get_chat_id_by_grpid( context, grpid, Some(&mut chat_id_blocked), @@ -1258,12 +1247,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); let self_addr = context .sql @@ -1385,13 +1374,13 @@ unsafe fn create_or_lookup_group( to_string(grpimage) }, ); - if let Ok(mut chat) = dc_chat_load_from_db(context, chat_id) { + 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)); } - dc_chat_update_param(&mut chat); + chat.update_param().unwrap(); send_EVENT_CHAT_MODIFIED = 1; } @@ -1419,14 +1408,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() { @@ -1434,11 +1423,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 { @@ -1448,7 +1437,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. @@ -1617,7 +1606,7 @@ unsafe fn create_or_lookup_adhoc_group( chat_id = create_group_record(context, grpid, grpname, create_blocked, 0); 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); diff --git a/src/dc_securejoin.rs b/src/dc_securejoin.rs index 88d67f34f..0f66fa171 100644 --- a/src/dc_securejoin.rs +++ b/src/dc_securejoin.rs @@ -4,7 +4,7 @@ use mmime::mailimf_types::*; use percent_encoding::{utf8_percent_encode, NON_ALPHANUMERIC}; use crate::aheader::EncryptPreference; -use crate::chat::*; +use crate::chat::{self, Chat}; use crate::constants::*; use crate::contact::*; use crate::context::Context; @@ -88,8 +88,8 @@ pub unsafe fn dc_get_securejoin_qr( let self_name_urlencoded = utf8_percent_encode(&self_name, NON_ALPHANUMERIC).to_string(); qr = if 0 != group_chat_id { - if let Ok(chat) = dc_get_chat(context, group_chat_id) { - group_name = dc_chat_get_name(&chat); + if let Ok(chat) = Chat::load_from_db(context, group_chat_id) { + group_name = chat.get_name(); group_name_urlencoded = dc_urlencode(group_name); Some(format!( @@ -152,7 +152,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 @@ -232,7 +233,7 @@ pub unsafe fn dc_join_securejoin(context: &Context, qr: *const libc::c_char) -> if bob.status == 1 { if 0 != join_vg { ret_chat_id = - dc_get_chat_id_by_grpid(context, (*qr_scan).text2, None, 0 as *mut libc::c_int) + chat::get_chat_id_by_grpid(context, (*qr_scan).text2, None, 0 as *mut libc::c_int) as libc::c_int } else { ret_chat_id = contact_chat_id as libc::c_int @@ -284,12 +285,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 { @@ -306,7 +308,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]) { @@ -339,8 +341,8 @@ 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 = Blocked::Not; + let contact_chat_id: u32; + let contact_chat_id_blocked: Blocked; let mut grpid: *mut libc::c_char = 0 as *mut libc::c_char; let mut ret: libc::c_int = 0i32; @@ -355,15 +357,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, - Blocked::Not, - &mut contact_chat_id, - Some(&mut contact_chat_id_blocked), - ); + 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 { - dc_unblock_chat(context, contact_chat_id); + chat::unblock(context, contact_chat_id); } ret = 0x2i32; if strcmp(step, b"vg-request\x00" as *const u8 as *const libc::c_char) == 0i32 @@ -574,7 +574,7 @@ pub unsafe fn dc_handle_securejoin_handshake( ); 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( + let group_chat_id: uint32_t = chat::get_chat_id_by_grpid( context, grpid, None, @@ -584,7 +584,7 @@ pub unsafe fn dc_handle_securejoin_handshake( error!(context, 0, "Chat {} not found.", as_str(grpid),); current_block = 4378276786830486580; } else { - dc_add_contact_to_chat_ex( + chat::add_contact_to_chat_ex( context, group_chat_id, contact_id, @@ -647,7 +647,12 @@ pub unsafe fn dc_handle_securejoin_handshake( 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(context, grpid, None, &mut is_verified_group); + chat::get_chat_id_by_grpid( + context, + grpid, + None, + &mut is_verified_group, + ); if 0 == is_verified_group { vg_expect_encrypted = 0i32 } @@ -803,7 +808,7 @@ unsafe fn secure_connection_established(context: &Context, contact_chat_id: uint }; let msg = CString::new(context.stock_string_repl_str(StockMessage::ContactVerified, addr)).unwrap(); - dc_add_device_msg(context, contact_chat_id, msg.as_ptr()); + chat::add_device_msg(context, contact_chat_id, msg.as_ptr()); context.call_cb( Event::CHAT_MODIFIED, contact_chat_id as uintptr_t, @@ -844,7 +849,7 @@ 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()); + chat::add_device_msg(context, contact_chat_id, msg_c.as_ptr()); error!(context, 0, "{} ({})", msg, as_str(details)); } @@ -906,8 +911,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 @@ -924,13 +927,10 @@ 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, - Blocked::Deaddrop, - &mut contact_chat_id, - None, - ); + 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 => "", @@ -939,7 +939,7 @@ pub unsafe fn dc_handle_degrade_event(context: &Context, peerstate: &Peerstate) context.stock_string_repl_str(StockMessage::ContactSetupChanged, peeraddr), ) .unwrap(); - dc_add_device_msg(context, contact_chat_id, msg.as_ptr()); + chat::add_device_msg(context, contact_chat_id, msg.as_ptr()); context.call_cb( Event::CHAT_MODIFIED, contact_chat_id as uintptr_t, diff --git a/src/dc_tools.rs b/src/dc_tools.rs index 22ac09582..6bbca2804 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}; @@ -637,7 +638,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 +653,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 +849,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 +864,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 +895,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 +930,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 +1144,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/peerstate.rs b/src/peerstate.rs index a2d2a74ec..b203904c7 100644 --- a/src/peerstate.rs +++ b/src/peerstate.rs @@ -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 f1e4d0d90..b3c7c1cf9 100644 --- a/tests/stress.rs +++ b/tests/stress.rs @@ -6,7 +6,7 @@ use std::ffi::CString; use mmime::mailimf_types::*; use tempfile::{tempdir, TempDir}; -use deltachat::chat::*; +use deltachat::chat::{self, Chat}; use deltachat::config; use deltachat::constants::*; use deltachat::contact::*; @@ -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,13 +759,13 @@ 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_load_from_db(&context.ctx, chat_id).unwrap(); + 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_load_from_db(&context.ctx, chat2_id).unwrap(); + let chat2 = Chat::load_from_db(&context.ctx, chat2_id).unwrap(); assert_eq!(as_str(chat2.name), as_str(chat.name)); }