diff --git a/deltachat-ffi/src/lib.rs b/deltachat-ffi/src/lib.rs index ffa3f17b4..f63feef7d 100644 --- a/deltachat-ffi/src/lib.rs +++ b/deltachat-ffi/src/lib.rs @@ -24,6 +24,7 @@ use std::sync::RwLock; use libc::uintptr_t; use num_traits::{FromPrimitive, ToPrimitive}; +use deltachat::chat::ChatId; use deltachat::constants::DC_MSG_ID_LAST_SPECIAL; use deltachat::contact::Contact; use deltachat::context::Context; @@ -151,12 +152,12 @@ impl ContextWrapper { ffi_cb( self, event_id, - chat_id as uintptr_t, + chat_id.to_u32() as uintptr_t, msg_id.to_u32() as uintptr_t, ); } Event::ChatModified(chat_id) => { - ffi_cb(self, event_id, chat_id as uintptr_t, 0); + ffi_cb(self, event_id, chat_id.to_u32() as uintptr_t, 0); } Event::ContactsChanged(id) | Event::LocationChanged(id) => { let id = id.unwrap_or_default(); @@ -191,7 +192,7 @@ impl ContextWrapper { ffi_cb( self, event_id, - chat_id as uintptr_t, + chat_id.to_u32() as uintptr_t, contact_id as uintptr_t, ); } @@ -707,7 +708,9 @@ pub unsafe extern "C" fn dc_create_chat_by_msg_id(context: *mut dc_context_t, ms ffi_context .with_inner(|ctx| { chat::create_by_msg_id(ctx, MsgId::new(msg_id)) - .unwrap_or_log_default(ctx, "Failed to create chat") + .log_err(ctx, "Failed to create chat from msg_id") + .map(|id| id.to_u32()) + .unwrap_or(0) }) .unwrap_or(0) } @@ -725,7 +728,9 @@ pub unsafe extern "C" fn dc_create_chat_by_contact_id( ffi_context .with_inner(|ctx| { chat::create_by_contact_id(ctx, contact_id) - .unwrap_or_log_default(ctx, "Failed to create chat") + .log_err(ctx, "Failed to create chat from contact_id") + .map(|id| id.to_u32()) + .unwrap_or(0) }) .unwrap_or(0) } @@ -741,7 +746,12 @@ pub unsafe extern "C" fn dc_get_chat_id_by_contact_id( } let ffi_context = &*context; ffi_context - .with_inner(|ctx| chat::get_by_contact_id(ctx, contact_id).unwrap_or(0)) + .with_inner(|ctx| { + chat::get_by_contact_id(ctx, contact_id) + .log_err(ctx, "Failed to get chat for contact_id") + .map(|id| id.to_u32()) + .unwrap_or(0) + }) .unwrap_or(0) } @@ -759,7 +769,7 @@ pub unsafe extern "C" fn dc_prepare_msg( let ffi_msg: &mut MessageWrapper = &mut *msg; ffi_context .with_inner(|ctx| { - chat::prepare_msg(ctx, chat_id, &mut ffi_msg.message) + chat::prepare_msg(ctx, ChatId::new(chat_id), &mut ffi_msg.message) .unwrap_or_log_default(ctx, "Failed to prepare message") }) .map(|msg_id| msg_id.to_u32()) @@ -780,7 +790,7 @@ pub unsafe extern "C" fn dc_send_msg( let ffi_msg = &mut *msg; ffi_context .with_inner(|ctx| { - chat::send_msg(ctx, chat_id, &mut ffi_msg.message) + chat::send_msg(ctx, ChatId::new(chat_id), &mut ffi_msg.message) .unwrap_or_log_default(ctx, "Failed to send message") }) .map(|msg_id| msg_id.to_u32()) @@ -801,7 +811,7 @@ pub unsafe extern "C" fn dc_send_text_msg( let text_to_send = to_string_lossy(text_to_send); ffi_context .with_inner(|ctx| { - chat::send_text_msg(ctx, chat_id, text_to_send) + chat::send_text_msg(ctx, ChatId::new(chat_id), text_to_send) .map(|msg_id| msg_id.to_u32()) .unwrap_or_log_default(ctx, "Failed to send text message") }) @@ -826,7 +836,7 @@ pub unsafe extern "C" fn dc_set_draft( Some(&mut ffi_msg.message) }; ffi_context - .with_inner(|ctx| chat::set_draft(ctx, chat_id, msg)) + .with_inner(|ctx| chat::set_draft(ctx, ChatId::new(chat_id), msg)) .unwrap_or(()) } @@ -901,7 +911,7 @@ pub unsafe extern "C" fn dc_get_draft(context: *mut dc_context_t, chat_id: u32) } let ffi_context = &*context; ffi_context - .with_inner(|ctx| match chat::get_draft(ctx, chat_id) { + .with_inner(|ctx| match chat::get_draft(ctx, ChatId::new(chat_id)) { Ok(Some(draft)) => { let ffi_msg = MessageWrapper { context, @@ -938,7 +948,7 @@ pub unsafe extern "C" fn dc_get_chat_msgs( ffi_context .with_inner(|ctx| { let arr = dc_array_t::from( - chat::get_chat_msgs(ctx, chat_id, flags, marker_flag) + chat::get_chat_msgs(ctx, ChatId::new(chat_id), flags, marker_flag) .iter() .map(|msg_id| msg_id.to_u32()) .collect::>(), @@ -956,7 +966,7 @@ pub unsafe extern "C" fn dc_get_msg_cnt(context: *mut dc_context_t, chat_id: u32 } let ffi_context = &*context; ffi_context - .with_inner(|ctx| chat::get_msg_cnt(ctx, chat_id) as libc::c_int) + .with_inner(|ctx| chat::get_msg_cnt(ctx, ChatId::new(chat_id)) as libc::c_int) .unwrap_or(0) } @@ -971,7 +981,7 @@ pub unsafe extern "C" fn dc_get_fresh_msg_cnt( } let ffi_context = &*context; ffi_context - .with_inner(|ctx| chat::get_fresh_msg_cnt(ctx, chat_id) as libc::c_int) + .with_inner(|ctx| chat::get_fresh_msg_cnt(ctx, ChatId::new(chat_id)) as libc::c_int) .unwrap_or(0) } @@ -1006,7 +1016,9 @@ pub unsafe extern "C" fn dc_marknoticed_chat(context: *mut dc_context_t, chat_id let ffi_context = &*context; ffi_context .with_inner(|ctx| { - chat::marknoticed_chat(ctx, chat_id).log_err(ctx, "Failed marknoticed chat") + chat::marknoticed_chat(ctx, ChatId::new(chat_id)) + .log_err(ctx, "Failed marknoticed chat") + .unwrap_or(()) }) .unwrap_or(()) } @@ -1020,7 +1032,9 @@ pub unsafe extern "C" fn dc_marknoticed_all_chats(context: *mut dc_context_t) { let ffi_context = &*context; ffi_context .with_inner(|ctx| { - chat::marknoticed_all_chats(ctx).log_err(ctx, "Failed marknoticed all chats") + chat::marknoticed_all_chats(ctx) + .log_err(ctx, "Failed marknoticed all chats") + .unwrap_or(()) }) .unwrap_or(()) } @@ -1054,10 +1068,16 @@ pub unsafe extern "C" fn dc_get_chat_media( ffi_context .with_inner(|ctx| { let arr = dc_array_t::from( - chat::get_chat_media(ctx, chat_id, msg_type, or_msg_type2, or_msg_type3) - .iter() - .map(|msg_id| msg_id.to_u32()) - .collect::>(), + chat::get_chat_media( + ctx, + ChatId::new(chat_id), + msg_type, + or_msg_type2, + or_msg_type3, + ) + .iter() + .map(|msg_id| msg_id.to_u32()) + .collect::>(), ); Box::into_raw(Box::new(arr)) }) @@ -1124,7 +1144,11 @@ pub unsafe extern "C" fn dc_archive_chat( return; }; ffi_context - .with_inner(|ctx| chat::archive(ctx, chat_id, archive).log_err(ctx, "Failed archive chat")) + .with_inner(|ctx| { + chat::archive(ctx, ChatId::new(chat_id), archive) + .log_err(ctx, "Failed archive chat") + .unwrap_or(()) + }) .unwrap_or(()) } @@ -1136,7 +1160,11 @@ pub unsafe extern "C" fn dc_delete_chat(context: *mut dc_context_t, chat_id: u32 } let ffi_context = &*context; ffi_context - .with_inner(|ctx| chat::delete(ctx, chat_id).log_err(ctx, "Failed chat delete")) + .with_inner(|ctx| { + chat::delete(ctx, ChatId::new(chat_id)) + .log_err(ctx, "Failed chat delete") + .unwrap_or(()) + }) .unwrap_or(()) } @@ -1152,7 +1180,7 @@ pub unsafe extern "C" fn dc_get_chat_contacts( let ffi_context = &*context; ffi_context .with_inner(|ctx| { - let arr = dc_array_t::from(chat::get_chat_contacts(ctx, chat_id)); + let arr = dc_array_t::from(chat::get_chat_contacts(ctx, ChatId::new(chat_id))); Box::into_raw(Box::new(arr)) }) .unwrap_or_else(|_| ptr::null_mut()) @@ -1172,7 +1200,7 @@ pub unsafe extern "C" fn dc_search_msgs( ffi_context .with_inner(|ctx| { let arr = dc_array_t::from( - ctx.search_msgs(chat_id, to_string_lossy(query)) + ctx.search_msgs(ChatId::new(chat_id), to_string_lossy(query)) .iter() .map(|msg_id| msg_id.to_u32()) .collect::>(), @@ -1190,13 +1218,15 @@ pub unsafe extern "C" fn dc_get_chat(context: *mut dc_context_t, chat_id: u32) - } let ffi_context = &*context; ffi_context - .with_inner(|ctx| match chat::Chat::load_from_db(ctx, chat_id) { - Ok(chat) => { - let ffi_chat = ChatWrapper { context, chat }; - Box::into_raw(Box::new(ffi_chat)) - } - Err(_) => ptr::null_mut(), - }) + .with_inner( + |ctx| match chat::Chat::load_from_db(ctx, ChatId::new(chat_id)) { + Ok(chat) => { + let ffi_chat = ChatWrapper { context, chat }; + Box::into_raw(Box::new(ffi_chat)) + } + Err(_) => ptr::null_mut(), + }, + ) .unwrap_or_else(|_| ptr::null_mut()) } @@ -1219,7 +1249,9 @@ pub unsafe extern "C" fn dc_create_group_chat( ffi_context .with_inner(|ctx| { chat::create_group_chat(ctx, verified, to_string_lossy(name)) - .unwrap_or_log_default(ctx, "Failed to create group chat") + .log_err(ctx, "Failed to create group chat") + .map(|id| id.to_u32()) + .unwrap_or(0) }) .unwrap_or(0) } @@ -1236,7 +1268,7 @@ pub unsafe extern "C" fn dc_is_contact_in_chat( } let ffi_context = &*context; ffi_context - .with_inner(|ctx| chat::is_contact_in_chat(ctx, chat_id, contact_id)) + .with_inner(|ctx| chat::is_contact_in_chat(ctx, ChatId::new(chat_id), contact_id)) .unwrap_or_default() .into() } @@ -1253,7 +1285,9 @@ pub unsafe extern "C" fn dc_add_contact_to_chat( } let ffi_context = &*context; ffi_context - .with_inner(|ctx| chat::add_contact_to_chat(ctx, chat_id, contact_id) as libc::c_int) + .with_inner(|ctx| { + chat::add_contact_to_chat(ctx, ChatId::new(chat_id), contact_id) as libc::c_int + }) .unwrap_or(0) } @@ -1270,7 +1304,7 @@ pub unsafe extern "C" fn dc_remove_contact_from_chat( let ffi_context = &*context; ffi_context .with_inner(|ctx| { - chat::remove_contact_from_chat(ctx, chat_id, contact_id) + chat::remove_contact_from_chat(ctx, ChatId::new(chat_id), contact_id) .map(|_| 1) .unwrap_or_log_default(ctx, "Failed to remove contact") }) @@ -1290,7 +1324,7 @@ pub unsafe extern "C" fn dc_set_chat_name( let ffi_context = &*context; ffi_context .with_inner(|ctx| { - chat::set_chat_name(ctx, chat_id, to_string_lossy(name)) + chat::set_chat_name(ctx, ChatId::new(chat_id), to_string_lossy(name)) .map(|_| 1) .unwrap_or_log_default(ctx, "Failed to set chat name") }) @@ -1310,7 +1344,7 @@ pub unsafe extern "C" fn dc_set_chat_profile_image( let ffi_context = &*context; ffi_context .with_inner(|ctx| { - chat::set_chat_profile_image(ctx, chat_id, to_string_lossy(image)) + chat::set_chat_profile_image(ctx, ChatId::new(chat_id), to_string_lossy(image)) .map(|_| 1) .unwrap_or_log_default(ctx, "Failed to set profile image") }) @@ -1399,7 +1433,7 @@ pub unsafe extern "C" fn dc_forward_msgs( let ffi_context = &*context; ffi_context .with_inner(|ctx| { - chat::forward_msgs(ctx, &msg_ids[..], chat_id) + chat::forward_msgs(ctx, &msg_ids[..], ChatId::new(chat_id)) .unwrap_or_log_default(ctx, "Failed to forward message") }) .unwrap_or_default() @@ -1812,7 +1846,7 @@ pub unsafe extern "C" fn dc_get_securejoin_qr( let ffi_context = &*context; ffi_context .with_inner(|ctx| { - securejoin::dc_get_securejoin_qr(ctx, chat_id) + securejoin::dc_get_securejoin_qr(ctx, ChatId::new(chat_id)) .unwrap_or_else(|| "".to_string()) .strdup() }) @@ -1830,7 +1864,7 @@ pub unsafe extern "C" fn dc_join_securejoin( } let ffi_context = &*context; ffi_context - .with_inner(|ctx| securejoin::dc_join_securejoin(ctx, &to_string_lossy(qr))) + .with_inner(|ctx| securejoin::dc_join_securejoin(ctx, &to_string_lossy(qr)).to_u32()) .unwrap_or(0) } @@ -1846,7 +1880,9 @@ pub unsafe extern "C" fn dc_send_locations_to_chat( } let ffi_context = &*context; ffi_context - .with_inner(|ctx| location::send_locations_to_chat(ctx, chat_id, seconds as i64)) + .with_inner(|ctx| { + location::send_locations_to_chat(ctx, ChatId::new(chat_id), seconds as i64) + }) .ok(); } @@ -1861,7 +1897,9 @@ pub unsafe extern "C" fn dc_is_sending_locations_to_chat( } let ffi_context = &*context; ffi_context - .with_inner(|ctx| location::is_sending_locations_to_chat(ctx, chat_id) as libc::c_int) + .with_inner(|ctx| { + location::is_sending_locations_to_chat(ctx, ChatId::new(chat_id)) as libc::c_int + }) .unwrap_or(0) } @@ -1899,7 +1937,7 @@ pub unsafe extern "C" fn dc_get_locations( .with_inner(|ctx| { let res = location::get_range( ctx, - chat_id, + ChatId::new(chat_id), contact_id, timestamp_begin as i64, timestamp_end as i64, @@ -2021,8 +2059,7 @@ pub unsafe extern "C" fn dc_array_get_chat_id( eprintln!("ignoring careless call to dc_array_get_chat_id()"); return 0; } - - (*array).get_location(index).chat_id + (*array).get_location(index).chat_id.to_u32() } #[no_mangle] pub unsafe extern "C" fn dc_array_get_contact_id( @@ -2159,7 +2196,7 @@ pub unsafe extern "C" fn dc_chatlist_get_chat_id( return 0; } let ffi_list = &*chatlist; - ffi_list.list.get_chat_id(index as usize) + ffi_list.list.get_chat_id(index as usize).to_u32() } #[no_mangle] @@ -2251,7 +2288,7 @@ pub unsafe extern "C" fn dc_chat_get_id(chat: *mut dc_chat_t) -> u32 { return 0; } let ffi_chat = &*chat; - ffi_chat.chat.get_id() + ffi_chat.chat.get_id().to_u32() } #[no_mangle] @@ -2398,7 +2435,7 @@ pub unsafe extern "C" fn dc_chat_get_info_json( let ffi_context = &*context; ffi_context .with_inner(|ctx| { - let chat = match chat::Chat::load_from_db(ctx, chat_id) { + let chat = match chat::Chat::load_from_db(ctx, ChatId::new(chat_id)) { Ok(chat) => chat, Err(err) => { error!(ctx, "dc_get_chat_info_json() failed to load chat: {}", err); @@ -2494,7 +2531,7 @@ pub unsafe extern "C" fn dc_msg_get_chat_id(msg: *mut dc_msg_t) -> u32 { return 0; } let ffi_msg = &*msg; - ffi_msg.message.get_chat_id() + ffi_msg.message.get_chat_id().to_u32() } #[no_mangle] @@ -3119,12 +3156,12 @@ pub unsafe extern "C" fn dc_str_unref(s: *mut libc::c_char) { pub mod providers; -pub trait ResultExt { +pub trait ResultExt { fn unwrap_or_log_default(self, context: &context::Context, message: &str) -> T; - fn log_err(&self, context: &context::Context, message: &str); + fn log_err(self, context: &context::Context, message: &str) -> Result; } -impl ResultExt for Result { +impl ResultExt for Result { fn unwrap_or_log_default(self, context: &context::Context, message: &str) -> T { match self { Ok(t) => t, @@ -3135,10 +3172,11 @@ impl ResultExt for Result { } } - fn log_err(&self, context: &context::Context, message: &str) { - if let Err(err) = self { - error!(context, "{}: {}", message, err); - } + fn log_err(self, context: &context::Context, message: &str) -> Result { + self.map_err(|err| { + warn!(context, "{}: {}", message, err); + err + }) } } diff --git a/examples/repl/cmdline.rs b/examples/repl/cmdline.rs index 0fd04092c..1355677fe 100644 --- a/examples/repl/cmdline.rs +++ b/examples/repl/cmdline.rs @@ -1,7 +1,7 @@ use std::path::Path; use std::str::FromStr; -use deltachat::chat::{self, Chat}; +use deltachat::chat::{self, Chat, ChatId}; use deltachat::chatlist::*; use deltachat::config; use deltachat::constants::*; @@ -84,7 +84,7 @@ fn dc_reset_tables(context: &Context, bits: i32) -> i32 { } context.call_cb(Event::MsgsChanged { - chat_id: 0, + chat_id: ChatId::new(0), msg_id: MsgId::new(0), }); @@ -166,7 +166,7 @@ fn poke_spec(context: &Context, spec: Option<&str>) -> libc::c_int { println!("Import: {} items read from \"{}\".", read_cnt, &real_spec); if read_cnt > 0 { context.call_cb(Event::MsgsChanged { - chat_id: 0, + chat_id: ChatId::new(0), msg_id: MsgId::new(0), }); } @@ -299,7 +299,7 @@ fn chat_prefix(chat: &Chat) -> &'static str { pub 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 { + let mut sel_chat = if !chat_id.is_unset() { Chat::load_from_db(context, chat_id).ok() } else { None @@ -549,7 +549,7 @@ pub fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::Error> { ); } } - if location::is_sending_locations_to_chat(context, 0) { + if location::is_sending_locations_to_chat(context, ChatId::new(0)) { println!("Location streaming enabled."); } println!("{} chats", cnt); @@ -559,8 +559,8 @@ pub fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::Error> { bail!("Argument [chat-id] is missing."); } if !arg1.is_empty() { - let chat_id = arg1.parse()?; - println!("Selecting chat #{}", chat_id); + let chat_id = ChatId::new(arg1.parse()?); + println!("Selecting chat {}", chat_id); sel_chat = Some(Chat::load_from_db(context, chat_id)?); *context.cmdline_sel_chat_id.write().unwrap() = chat_id; } @@ -787,7 +787,7 @@ pub fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::Error> { let chat = if let Some(ref sel_chat) = sel_chat { sel_chat.get_id() } else { - 0 as libc::c_uint + ChatId::new(0) }; let msglist = context.search_msgs(chat, arg1); @@ -846,12 +846,12 @@ pub fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::Error> { } "archive" | "unarchive" => { ensure!(!arg1.is_empty(), "Argument missing."); - let chat_id = arg1.parse()?; + let chat_id = ChatId::new(arg1.parse()?); chat::archive(context, chat_id, arg0 == "archive")?; } "delchat" => { ensure!(!arg1.is_empty(), "Argument missing."); - let chat_id = arg1.parse()?; + let chat_id = ChatId::new(arg1.parse()?); chat::delete(context, chat_id)?; } "msginfo" => { @@ -873,7 +873,7 @@ pub fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::Error> { ); let mut msg_ids = [MsgId::new(0); 1]; - let chat_id = arg2.parse()?; + let chat_id = ChatId::new(arg2.parse()?); msg_ids[0] = MsgId::new(arg1.parse()?); chat::forward_msgs(context, &msg_ids, chat_id)?; } diff --git a/examples/repl/main.rs b/examples/repl/main.rs index f7e4d557f..25a2ab166 100644 --- a/examples/repl/main.rs +++ b/examples/repl/main.rs @@ -20,6 +20,7 @@ use std::process::Command; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::{Arc, Mutex, RwLock}; +use deltachat::chat::ChatId; use deltachat::config; use deltachat::configure::*; use deltachat::context::*; @@ -484,9 +485,10 @@ fn handle_cmd(line: &str, ctx: Arc>) -> Result { start_threads(ctx.clone()); - if let Some(mut qr) = - dc_get_securejoin_qr(&ctx.read().unwrap(), arg1.parse().unwrap_or_default()) - { + if let Some(mut qr) = dc_get_securejoin_qr( + &ctx.read().unwrap(), + ChatId::new(arg1.parse().unwrap_or_default()), + ) { if !qr.is_empty() { if arg0 == "getbadqr" && qr.len() > 40 { qr.replace_range(12..22, "0000000000") diff --git a/src/chat.rs b/src/chat.rs index 6f223866c..53bc8983f 100644 --- a/src/chat.rs +++ b/src/chat.rs @@ -22,13 +22,149 @@ use crate::param::*; use crate::sql; use crate::stock::StockMessage; +/// Chat ID, including reserved IDs. +/// +/// Some chat IDs are reserved to identify special chat types. This +/// type can represent both the special as well as normal chats. +#[derive(Debug, Copy, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub struct ChatId(u32); + +impl ChatId { + /// Create a new [ChatId]. + pub fn new(id: u32) -> ChatId { + ChatId(id) + } + + /// A ChatID which indicates an error. + /// + /// This is transitional and should not be used in new code. Do + /// not represent errors in a ChatId. + pub fn is_error(self) -> bool { + self.0 == 0 + } + + /// An unset ChatId + /// + /// Like [is_error], from which it is indistinguishable, this is + /// transitional and should not be used in new code. + pub fn is_unset(self) -> bool { + self.0 == 0 + } + + /// Whether the chat ID signifies a special chat. + /// + /// This kind of chat ID can not be used for real chats. + pub fn is_special(self) -> bool { + match self.0 { + 0..=DC_CHAT_ID_LAST_SPECIAL => true, + _ => false, + } + } + + /// Chat ID which represents the deaddrop chat. + /// + /// This is a virtual chat showing all messages belonging to chats + /// flagged with [Blocked::Deaddrop]. Usually the UI will show + /// these messages as contact requests. + pub fn is_deaddrop(self) -> bool { + self.0 == DC_CHAT_ID_DEADDROP + } + + /// Chat ID for messages which need to be deleted. + /// + /// Messages which should be deleted get this chat ID and are + /// deleted later. Deleted messages need to stay around as long + /// as they are not deleted on the server so that their rfc724_mid + /// remains known and downloading them again can be avoided. + pub fn is_trash(self) -> bool { + self.0 == DC_CHAT_ID_TRASH + } + + // DC_CHAT_ID_MSGS_IN_CREATION seems unused? + + /// Virtual chat showing all starred messages. + pub fn is_starred(self) -> bool { + self.0 == DC_CHAT_ID_STARRED + } + + /// Chat ID signifying there are **any** number of archived chats. + /// + /// This chat ID can be returned in a [Chatlist] and signals to + /// the UI to include a link to the archived chats. + pub fn is_archived_link(self) -> bool { + self.0 == DC_CHAT_ID_ARCHIVED_LINK + } + + /// Virtual chat ID signalling there are **only** archived chats. + /// + /// This can be included in the chatlist if the + /// [DC_GCL_ADD_ALLDONE_HINT] flag is used to build the + /// [Chatlist]. + pub fn is_alldone_hint(self) -> bool { + self.0 == DC_CHAT_ID_ALLDONE_HINT + } + + /// Bad evil escape hatch. + /// + /// Avoid using this, eventually types should be cleaned up enough + /// that it is no longer necessary. + pub fn to_u32(self) -> u32 { + self.0 + } +} + +impl std::fmt::Display for ChatId { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + if self.is_deaddrop() { + write!(f, "Chat#Deadrop") + } else if self.is_trash() { + write!(f, "Chat#Trash") + } else if self.is_starred() { + write!(f, "Chat#Starred") + } else if self.is_archived_link() { + write!(f, "Chat#ArchivedLink") + } else if self.is_alldone_hint() { + write!(f, "Chat#AlldoneHint") + } else if self.is_special() { + write!(f, "Chat#Special{}", self.0) + } else { + write!(f, "Chat#{}", self.0) + } + } +} + +/// Allow converting [ChatId] to an SQLite type. +/// +/// This allows you to directly store [ChatId] into the database as +/// well as query for a [ChatId]. +impl rusqlite::types::ToSql for ChatId { + fn to_sql(&self) -> rusqlite::Result { + let val = rusqlite::types::Value::Integer(self.0 as i64); + let out = rusqlite::types::ToSqlOutput::Owned(val); + Ok(out) + } +} + +/// Allow converting an SQLite integer directly into [ChatId]. +impl rusqlite::types::FromSql for ChatId { + fn column_result(value: rusqlite::types::ValueRef) -> rusqlite::types::FromSqlResult { + i64::column_result(value).and_then(|val| { + if 0 <= val && val <= std::u32::MAX as i64 { + Ok(ChatId::new(val as u32)) + } else { + Err(rusqlite::types::FromSqlError::OutOfRange(val)) + } + }) + } +} + /// An object representing a single chat in memory. /// Chat objects are created using eg. `Chat::load_from_db` /// and are not updated on database changes; /// if you want an update, you have to recreate the object. #[derive(Debug, Clone)] pub struct Chat { - pub id: u32, + pub id: ChatId, pub typ: Chattype, pub name: String, archived: bool, @@ -40,24 +176,24 @@ pub struct Chat { impl Chat { /// Loads chat from the database by its ID. - pub fn load_from_db(context: &Context, chat_id: u32) -> Result { + pub fn load_from_db(context: &Context, chat_id: ChatId) -> Result { let res = context.sql.query_row( - "SELECT c.id,c.type,c.name, c.grpid,c.param,c.archived, \ - c.blocked, c.locations_send_until \ - FROM chats c WHERE c.id=?;", - params![chat_id as i32], + "SELECT c.type, c.name, c.grpid, c.param, c.archived, + c.blocked, c.locations_send_until + FROM chats c + WHERE c.id=?;", + params![chat_id], |row| { let c = Chat { - id: row.get(0)?, - typ: row.get(1)?, - name: row.get::<_, String>(2)?, - grpid: row.get::<_, String>(3)?, - param: row.get::<_, String>(4)?.parse().unwrap_or_default(), - archived: row.get(5)?, - blocked: row.get::<_, Option<_>>(6)?.unwrap_or_default(), - is_sending_locations: row.get(7)?, + id: chat_id, + typ: row.get(0)?, + name: row.get::<_, String>(1)?, + grpid: row.get::<_, String>(2)?, + param: row.get::<_, String>(3)?.parse().unwrap_or_default(), + archived: row.get(4)?, + blocked: row.get::<_, Option<_>>(5)?.unwrap_or_default(), + is_sending_locations: row.get(6)?, }; - Ok(c) }, ); @@ -74,37 +210,29 @@ impl Chat { Err(err.into()) } Ok(mut chat) => { - match chat.id { - DC_CHAT_ID_DEADDROP => { - chat.name = context.stock_str(StockMessage::DeadDrop).into(); - } - DC_CHAT_ID_ARCHIVED_LINK => { - let tempname = context.stock_str(StockMessage::ArchivedChats); - let cnt = dc_get_archived_cnt(context); - chat.name = format!("{} ({})", tempname, cnt); - } - DC_CHAT_ID_STARRED => { - chat.name = context.stock_str(StockMessage::StarredMsgs).into(); - } - _ => { - if chat.typ == Chattype::Single { - let contacts = get_chat_contacts(context, chat.id); - let mut chat_name = "Err [Name not found]".to_owned(); - - if let Some(contact_id) = contacts.first() { - if let Ok(contact) = Contact::get_by_id(context, *contact_id) { - chat_name = contact.get_display_name().to_owned(); - } + if chat.id.is_deaddrop() { + chat.name = context.stock_str(StockMessage::DeadDrop).into(); + } else if chat.id.is_archived_link() { + let tempname = context.stock_str(StockMessage::ArchivedChats); + let cnt = dc_get_archived_cnt(context); + chat.name = format!("{} ({})", tempname, cnt); + } else if chat.id.is_starred() { + chat.name = context.stock_str(StockMessage::StarredMsgs).into(); + } else { + if chat.typ == Chattype::Single { + let contacts = get_chat_contacts(context, chat.id); + let mut chat_name = "Err [Name not found]".to_owned(); + if let Some(contact_id) = contacts.first() { + if let Ok(contact) = Contact::get_by_id(context, *contact_id) { + chat_name = contact.get_display_name().to_owned(); } - - chat.name = chat_name; - } - - if chat.param.exists(Param::Selftalk) { - chat.name = context.stock_str(StockMessage::SavedMessages).into(); - } else if chat.param.exists(Param::Devicetalk) { - chat.name = context.stock_str(StockMessage::DeviceMessages).into(); } + chat.name = chat_name; + } + if chat.param.exists(Param::Selftalk) { + chat.name = context.stock_str(StockMessage::SavedMessages).into(); + } else if chat.param.exists(Param::Devicetalk) { + chat.name = context.stock_str(StockMessage::DeviceMessages).into(); } } Ok(chat) @@ -123,7 +251,7 @@ impl Chat { /// Returns true if user can send messages to this chat. pub fn can_send(&self) -> bool { - self.id > DC_CHAT_ID_LAST_SPECIAL && !self.is_device_talk() + !self.id.is_special() && !self.is_device_talk() } pub fn update_param(&mut self, context: &Context) -> Result<(), Error> { @@ -131,13 +259,13 @@ impl Chat { context, &context.sql, "UPDATE chats SET param=? WHERE id=?", - params![self.param.to_string(), self.id as i32], + params![self.param.to_string(), self.id], )?; Ok(()) } /// Returns chat ID. - pub fn get_id(&self) -> u32 { + pub fn get_id(&self) -> ChatId { self.id } @@ -163,16 +291,17 @@ impl Chat { .sql .query_get_value( 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], + "SELECT c.addr + FROM chats_contacts cc + LEFT JOIN contacts c ON c.id=cc.contact_id + WHERE cc.chat_id=?;", + params![self.id], ) .unwrap_or_else(|| "Err".into()); } if self.typ == Chattype::Group || self.typ == Chattype::VerifiedGroup { - if self.id == DC_CHAT_ID_DEADDROP { + if self.id.is_deaddrop() { return context.stock_str(StockMessage::DeadDrop).into(); } let cnt = get_chat_contact_cnt(context, self.id); @@ -196,7 +325,7 @@ impl Chat { fn get_parent_mime_headers(&self, context: &Context) -> Option<(String, String, String)> { let collect = |row: &rusqlite::Row| Ok((row.get(0)?, row.get(1)?, row.get(2)?)); - let params = params![self.id as i32]; + let params = params![self.id]; let sql = &context.sql; let query = Self::parent_query("rfc724_mid, mime_in_reply_to, mime_references"); @@ -206,7 +335,7 @@ impl Chat { fn parent_is_encrypted(&self, context: &Context) -> bool { let sql = &context.sql; - let params = params![self.id as i32]; + let params = params![self.id]; let query = Self::parent_query("param"); let packed: Option = sql.query_get_value(context, &query, params); @@ -352,18 +481,15 @@ impl Chat { if let Some(id) = context.sql.query_get_value( context, "SELECT contact_id FROM chats_contacts WHERE chat_id=?;", - params![self.id as i32], + params![self.id], ) { to_id = id; } else { error!( context, - "Cannot send message, contact for chat #{} not found.", self.id, - ); - bail!( - "Cannot set message, contact for chat #{} not found.", - self.id + "Cannot send message, contact for {} not found.", self.id, ); + bail!("Cannot set message, contact for {} not found.", self.id); } } else if (self.typ == Chattype::Group || self.typ == Chattype::VerifiedGroup) && self.param.get_int(Param::Unpromoted).unwrap_or_default() == 1 @@ -479,7 +605,7 @@ impl Chat { params![ timestamp, DC_CONTACT_ID_SELF, - self.id as i32, + self.id, msg.param.get_float(Param::SetLatitude).unwrap_or_default(), msg.param.get_float(Param::SetLongitude).unwrap_or_default(), ], @@ -505,7 +631,7 @@ impl Chat { "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![ new_rfc724_mid, - self.id as i32, + self.id, DC_CONTACT_ID_SELF, to_id as i32, timestamp, @@ -529,7 +655,7 @@ impl Chat { } else { error!( context, - "Cannot send message, cannot insert to database (chat #{}).", + "Cannot send message, cannot insert to database ({}).", self.id, ); } @@ -546,7 +672,7 @@ impl Chat { #[non_exhaustive] pub struct ChatInfo { /// The chat ID. - pub id: u32, + pub id: ChatId, /// The type of chat as a `u32` representation of [Chattype]. /// @@ -625,11 +751,11 @@ pub struct ChatInfo { /// # Returns /// /// The "created" chat ID is returned. -pub fn create_by_msg_id(context: &Context, msg_id: MsgId) -> Result { +pub fn create_by_msg_id(context: &Context, msg_id: MsgId) -> Result { let msg = Message::load_from_db(context, msg_id)?; let chat = Chat::load_from_db(context, msg.chat_id)?; ensure!( - chat.id > DC_CHAT_ID_LAST_SPECIAL, + !chat.id.is_special(), "Message can not belong to a special chat" ); if chat.blocked != Blocked::Not { @@ -637,7 +763,7 @@ pub fn create_by_msg_id(context: &Context, msg_id: MsgId) -> Result // Sending with 0s as data since multiple messages may have changed. context.call_cb(Event::MsgsChanged { - chat_id: 0, + chat_id: ChatId::new(0), msg_id: MsgId::new(0), }); } @@ -651,7 +777,7 @@ pub fn create_by_msg_id(context: &Context, msg_id: MsgId) -> Result /// 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 { +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 { @@ -678,27 +804,27 @@ pub fn create_by_contact_id(context: &Context, contact_id: u32) -> Result bool { - if chat_id == 0 { - warn!(context, "ignoring setting of Block-status for chat_id=0"); +pub fn set_blocking(context: &Context, chat_id: ChatId, new_blocking: Blocked) -> bool { + if chat_id.is_special() { + warn!(context, "ignoring setting of Block-status for {}", chat_id); return false; } sql::execute( context, &context.sql, "UPDATE chats SET blocked=? WHERE id=?;", - params![new_blocking, chat_id as i32], + params![new_blocking, chat_id], ) .is_ok() } @@ -761,7 +887,7 @@ pub fn create_or_lookup_by_contact_id( context: &Context, contact_id: u32, create_blocked: Blocked, -) -> Result<(u32, Blocked), Error> { +) -> Result<(ChatId, Blocked), Error> { ensure!(context.sql.is_open(), "Database not available"); ensure!(contact_id > 0, "Invalid contact id requested"); @@ -791,7 +917,8 @@ pub fn create_or_lookup_by_contact_id( ] )?; - let chat_id = sql::get_rowid(context, &context.sql, "chats", "grpid", contact.get_addr()); + let row_id = sql::get_rowid(context, &context.sql, "chats", "grpid", contact.get_addr()); + let chat_id = ChatId::new(row_id); sql::execute( context, @@ -809,17 +936,34 @@ pub fn create_or_lookup_by_contact_id( Ok((chat_id, create_blocked)) } -pub fn lookup_by_contact_id(context: &Context, contact_id: u32) -> Result<(u32, Blocked), Error> { +pub fn lookup_by_contact_id( + context: &Context, + contact_id: u32, +) -> Result<(ChatId, 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())), - ).map_err(Into::into) + 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::<_, ChatId>(0)?, + row.get::<_, Option<_>>(1)?.unwrap_or_default(), + )) + }, + ) + .map_err(Into::into) } -pub fn get_by_contact_id(context: &Context, contact_id: u32) -> Result { +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"); @@ -828,11 +972,11 @@ pub fn get_by_contact_id(context: &Context, contact_id: u32) -> Result( context: &'a Context, - chat_id: u32, + chat_id: ChatId, msg: &mut Message, ) -> Result { ensure!( - chat_id > DC_CHAT_ID_LAST_SPECIAL, + !chat_id.is_special(), "Cannot prepare message for special chat" ); @@ -902,13 +1046,17 @@ fn prepare_msg_blob(context: &Context, msg: &mut Message) -> Result<(), Error> { Ok(()) } -fn prepare_msg_common(context: &Context, chat_id: u32, msg: &mut Message) -> Result { +fn prepare_msg_common( + context: &Context, + chat_id: ChatId, + msg: &mut Message, +) -> Result { msg.id = MsgId::new_unset(); prepare_msg_blob(context, msg)?; unarchive(context, chat_id)?; let mut chat = Chat::load_from_db(context, chat_id)?; - ensure!(chat.can_send(), "cannot send to chat #{}", chat_id); + ensure!(chat.can_send(), "cannot send to {}", chat_id); // The OutPreparing state is set by dc_prepare_msg() before it // calls this function and the message is left in the OutPreparing @@ -925,7 +1073,7 @@ fn prepare_msg_common(context: &Context, chat_id: u32, msg: &mut Message) -> Res } /// Returns whether a contact is in a chat or not. -pub fn is_contact_in_chat(context: &Context, chat_id: u32, contact_id: u32) -> bool { +pub fn is_contact_in_chat(context: &Context, chat_id: ChatId, contact_id: u32) -> bool { /* 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) */ @@ -933,19 +1081,19 @@ pub fn is_contact_in_chat(context: &Context, chat_id: u32, contact_id: u32) -> b .sql .exists( "SELECT contact_id FROM chats_contacts WHERE chat_id=? AND contact_id=?;", - params![chat_id as i32, contact_id as i32], + params![chat_id, contact_id as i32], ) .unwrap_or_default() } // note that unarchive() is not the same as archive(false) - // eg. unarchive() does not send events as done for archive(false). -pub fn unarchive(context: &Context, chat_id: u32) -> Result<(), Error> { +pub fn unarchive(context: &Context, chat_id: ChatId) -> Result<(), Error> { sql::execute( context, &context.sql, "UPDATE chats SET archived=0 WHERE id=?", - params![chat_id as i32], + params![chat_id], )?; Ok(()) } @@ -956,7 +1104,10 @@ pub fn unarchive(context: &Context, chat_id: u32) -> Result<(), Error> { /// 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 fn send_msg(context: &Context, chat_id: u32, msg: &mut Message) -> Result { +// TODO: Do not allow ChatId to be 0, if prepare_msg had been called +// the caller can get it from msg.chat_id. Forwards would need to +// be fixed for this somehow too. +pub fn send_msg(context: &Context, chat_id: ChatId, msg: &mut Message) -> Result { // dc_prepare_msg() leaves the message state to OutPreparing, we // only have to change the state to OutPending in this case. // Otherwise we still have to prepare the message, which will set @@ -967,7 +1118,7 @@ pub fn send_msg(context: &Context, chat_id: u32, msg: &mut Message) -> Result Result Result Result Result { ensure!( - chat_id > DC_CHAT_ID_LAST_SPECIAL, - "bad chat_id = {} <= DC_CHAT_ID_LAST_SPECIAL", + !chat_id.is_special(), + "bad chat_id, can not be a special chat: {}", chat_id ); @@ -1023,8 +1174,8 @@ pub fn send_text_msg( } // passing `None` as message jsut deletes the draft -pub fn set_draft(context: &Context, chat_id: u32, msg: Option<&mut Message>) { - if chat_id <= DC_CHAT_ID_LAST_SPECIAL { +pub fn set_draft(context: &Context, chat_id: ChatId, msg: Option<&mut Message>) { + if chat_id.is_special() { return; } @@ -1044,7 +1195,7 @@ pub fn set_draft(context: &Context, chat_id: u32, msg: Option<&mut Message>) { /// Delete draft message in specified chat, if there is one. /// /// Return {true}, if message was deleted, {false} otherwise. -fn maybe_delete_draft(context: &Context, chat_id: u32) -> bool { +fn maybe_delete_draft(context: &Context, chat_id: ChatId) -> bool { match get_draft_msg_id(context, chat_id) { Some(msg_id) => { Message::delete_from_db(context, msg_id); @@ -1057,7 +1208,7 @@ fn maybe_delete_draft(context: &Context, chat_id: u32) -> bool { /// Set provided message as draft message for specified chat. /// /// Return true on success, false on database error. -fn do_set_draft(context: &Context, chat_id: u32, msg: &mut Message) -> Result<(), Error> { +fn do_set_draft(context: &Context, chat_id: ChatId, msg: &mut Message) -> Result<(), Error> { match msg.viewtype { Viewtype::Unknown => bail!("Can not set draft of unknown type."), Viewtype::Text => match msg.text.as_ref() { @@ -1079,10 +1230,10 @@ fn do_set_draft(context: &Context, chat_id: u32, msg: &mut Message) -> Result<() sql::execute( context, &context.sql, - "INSERT INTO msgs (chat_id, from_id, timestamp, type, state, txt, param, hidden) \ + "INSERT INTO msgs (chat_id, from_id, timestamp, type, state, txt, param, hidden) VALUES (?,?,?, ?,?,?,?,?);", params![ - chat_id as i32, + chat_id, DC_CONTACT_ID_SELF, time(), msg.viewtype, @@ -1096,7 +1247,7 @@ fn do_set_draft(context: &Context, chat_id: u32, msg: &mut Message) -> Result<() } // similar to as dc_set_draft() but does not emit an event -fn set_draft_raw(context: &Context, chat_id: u32, msg: &mut Message) -> bool { +fn set_draft_raw(context: &Context, chat_id: ChatId, msg: &mut Message) -> bool { let deleted = maybe_delete_draft(context, chat_id); let set = do_set_draft(context, chat_id, msg).is_ok(); @@ -1104,16 +1255,16 @@ fn set_draft_raw(context: &Context, chat_id: u32, msg: &mut Message) -> bool { deleted || set } -fn get_draft_msg_id(context: &Context, chat_id: u32) -> Option { +fn get_draft_msg_id(context: &Context, chat_id: ChatId) -> Option { context.sql.query_get_value::<_, MsgId>( context, "SELECT id FROM msgs WHERE chat_id=? AND state=?;", - params![chat_id as i32, MessageState::OutDraft], + params![chat_id, MessageState::OutDraft], ) } -pub fn get_draft(context: &Context, chat_id: u32) -> Result, Error> { - if chat_id <= DC_CHAT_ID_LAST_SPECIAL { +pub fn get_draft(context: &Context, chat_id: ChatId) -> Result, Error> { + if chat_id.is_special() { return Ok(None); } match get_draft_msg_id(context, chat_id) { @@ -1124,7 +1275,7 @@ pub fn get_draft(context: &Context, chat_id: u32) -> Result, Err pub fn get_chat_msgs( context: &Context, - chat_id: u32, + chat_id: ChatId, flags: u32, marker1before: Option, ) -> Vec { @@ -1153,55 +1304,49 @@ pub fn get_chat_msgs( } Ok(ret) }; - let success = if chat_id == DC_CHAT_ID_DEADDROP { + let success = if chat_id.is_deaddrop() { let show_emails = ShowEmails::from_i32(context.get_config_int(Config::ShowEmails)).unwrap_or_default(); context.sql.query_map( - concat!( - "SELECT m.id AS id, m.timestamp AS timestamp", - " FROM msgs m", - " LEFT JOIN chats", - " ON m.chat_id=chats.id", - " LEFT JOIN contacts", - " ON m.from_id=contacts.id", - " WHERE m.from_id!=1", // 1=DC_CONTACT_ID_SELF - " AND m.from_id!=2", // 2=DC_CONTACT_ID_INFO - " AND m.hidden=0", - " AND chats.blocked=2", - " AND contacts.blocked=0", - " AND m.msgrmsg>=?", - " ORDER BY m.timestamp,m.id;" - ), + "SELECT m.id AS id, m.timestamp AS timestamp + FROM msgs m + LEFT JOIN chats + ON m.chat_id=chats.id + LEFT JOIN contacts + ON m.from_id=contacts.id + WHERE m.from_id!=1 -- 1=DC_CONTACT_ID_SELF + AND m.from_id!=2 -- 2=DC_CONTACT_ID_INFO + AND m.hidden=0 + AND chats.blocked=2 + AND contacts.blocked=0 + AND m.msgrmsg>=? + ORDER BY m.timestamp,m.id;", params![if show_emails == ShowEmails::All { 0 } else { 1 }], process_row, process_rows, ) - } else if chat_id == DC_CHAT_ID_STARRED { + } else if chat_id.is_starred() { context.sql.query_map( - concat!( - "SELECT m.id AS id, m.timestamp AS timestamp", - " FROM msgs m", - " LEFT JOIN contacts ct", - " ON m.from_id=ct.id", - " WHERE m.starred=1", - " AND m.hidden=0", - " AND ct.blocked=0", - " ORDER BY m.timestamp,m.id;" - ), + "SELECT m.id AS id, m.timestamp AS timestamp + FROM msgs m + LEFT JOIN contacts ct + ON m.from_id=ct.id + WHERE m.starred=1 + AND m.hidden=0 + AND ct.blocked=0 + ORDER BY m.timestamp,m.id;", params![], process_row, process_rows, ) } else { context.sql.query_map( - concat!( - "SELECT m.id AS id, m.timestamp AS timestamp", - " FROM msgs m", - " WHERE m.chat_id=?", - " AND m.hidden=0", - " ORDER BY m.timestamp, m.id;" - ), - params![chat_id as i32], + "SELECT m.id AS id, m.timestamp AS timestamp + FROM msgs m + WHERE m.chat_id=? + AND m.hidden=0 + ORDER BY m.timestamp, m.id;", + params![chat_id], process_row, process_rows, ) @@ -1216,35 +1361,36 @@ pub fn get_chat_msgs( } /// Returns number of messages in a chat. -pub fn get_msg_cnt(context: &Context, chat_id: u32) -> usize { +pub fn get_msg_cnt(context: &Context, chat_id: ChatId) -> usize { context .sql .query_get_value::<_, i32>( context, "SELECT COUNT(*) FROM msgs WHERE chat_id=?;", - params![chat_id as i32], + params![chat_id], ) .unwrap_or_default() as usize } -pub fn get_fresh_msg_cnt(context: &Context, chat_id: u32) -> usize { +pub fn get_fresh_msg_cnt(context: &Context, chat_id: ChatId) -> usize { context .sql .query_get_value::<_, i32>( context, - "SELECT COUNT(*) FROM msgs \ - WHERE state=10 \ - AND hidden=0 \ - AND chat_id=?;", - params![chat_id as i32], + "SELECT COUNT(*) + FROM msgs + WHERE state=10 + AND hidden=0 + AND chat_id=?;", + params![chat_id], ) .unwrap_or_default() as usize } -pub fn marknoticed_chat(context: &Context, chat_id: u32) -> Result<(), Error> { +pub fn marknoticed_chat(context: &Context, chat_id: ChatId) -> Result<(), Error> { if !context.sql.exists( "SELECT id FROM msgs WHERE chat_id=? AND state=?;", - params![chat_id as i32, MessageState::InFresh], + params![chat_id, MessageState::InFresh], )? { return Ok(()); } @@ -1252,13 +1398,15 @@ pub fn marknoticed_chat(context: &Context, chat_id: u32) -> Result<(), Error> { sql::execute( context, &context.sql, - "UPDATE msgs \ - SET state=13 WHERE chat_id=? AND state=10;", - params![chat_id as i32], + "UPDATE msgs + SET state=13 + WHERE chat_id=? + AND state=10;", + params![chat_id], )?; context.call_cb(Event::MsgsChanged { - chat_id: 0, + chat_id: ChatId::new(0), msg_id: MsgId::new(0), }); @@ -1267,8 +1415,9 @@ pub fn marknoticed_chat(context: &Context, chat_id: u32) -> Result<(), Error> { pub fn marknoticed_all_chats(context: &Context) -> Result<(), Error> { if !context.sql.exists( - "SELECT id FROM msgs \ - WHERE state=10;", + "SELECT id + FROM msgs + WHERE state=10;", params![], )? { return Ok(()); @@ -1277,14 +1426,15 @@ pub fn marknoticed_all_chats(context: &Context) -> Result<(), Error> { sql::execute( context, &context.sql, - "UPDATE msgs \ - SET state=13 WHERE state=10;", + "UPDATE msgs + SET state=13 + WHERE state=10;", params![], )?; context.call_cb(Event::MsgsChanged { msg_id: MsgId::new(0), - chat_id: 0, + chat_id: ChatId::new(0), }); Ok(()) @@ -1292,23 +1442,22 @@ pub fn marknoticed_all_chats(context: &Context) -> Result<(), Error> { pub fn get_chat_media( context: &Context, - chat_id: u32, + chat_id: ChatId, msg_type: Viewtype, msg_type2: Viewtype, msg_type3: Viewtype, ) -> Vec { + // TODO This query could/should be converted to `AND type IN (?, ?, ?)`. context .sql .query_map( - concat!( - "SELECT", - " id", - " FROM msgs", - " WHERE chat_id=? AND (type=? OR type=? OR type=?)", - " ORDER BY timestamp, id;" - ), + "SELECT id + FROM msgs + WHERE chat_id=? + AND (type=? OR type=? OR type=?) + ORDER BY timestamp, id;", params![ - chat_id as i32, + chat_id, msg_type, if msg_type2 != Viewtype::Unknown { msg_type2 @@ -1387,10 +1536,10 @@ pub fn get_next_media( } /// Archives or unarchives a chat. -pub fn archive(context: &Context, chat_id: u32, archive: bool) -> Result<(), Error> { +pub fn archive(context: &Context, chat_id: ChatId, archive: bool) -> Result<(), Error> { ensure!( - chat_id > DC_CHAT_ID_LAST_SPECIAL, - "bad chat_id = {} <= DC_CHAT_ID_LAST_SPECIAL", + !chat_id.is_special(), + "bad chat_id, can not be special chat: {}", chat_id ); @@ -1399,11 +1548,7 @@ pub fn archive(context: &Context, chat_id: u32, archive: bool) -> Result<(), Err context, &context.sql, "UPDATE msgs SET state=? WHERE chat_id=? AND state=?;", - params![ - MessageState::InNoticed, - chat_id as i32, - MessageState::InFresh - ], + params![MessageState::InNoticed, chat_id, MessageState::InFresh], )?; } @@ -1411,22 +1556,22 @@ pub fn archive(context: &Context, chat_id: u32, archive: bool) -> Result<(), Err context, &context.sql, "UPDATE chats SET archived=? WHERE id=?;", - params![archive, chat_id as i32], + params![archive, chat_id], )?; context.call_cb(Event::MsgsChanged { msg_id: MsgId::new(0), - chat_id: 0, + chat_id: ChatId::new(0), }); Ok(()) } /// Deletes a chat. -pub fn delete(context: &Context, chat_id: u32) -> Result<(), Error> { +pub fn delete(context: &Context, chat_id: ChatId) -> Result<(), Error> { ensure!( - chat_id > DC_CHAT_ID_LAST_SPECIAL, - "bad chat_id = {} <= DC_CHAT_ID_LAST_SPECIAL", + !chat_id.is_special(), + "bad chat_id, can not be a special chat: {}", chat_id ); /* Up to 2017-11-02 deleting a group also implied leaving it, see above why we have changed this. */ @@ -1436,33 +1581,33 @@ pub fn delete(context: &Context, chat_id: u32) -> Result<(), Error> { context, &context.sql, "DELETE FROM msgs_mdns WHERE msg_id IN (SELECT id FROM msgs WHERE chat_id=?);", - params![chat_id as i32], + params![chat_id], )?; sql::execute( context, &context.sql, "DELETE FROM msgs WHERE chat_id=?;", - params![chat_id as i32], + params![chat_id], )?; sql::execute( context, &context.sql, "DELETE FROM chats_contacts WHERE chat_id=?;", - params![chat_id as i32], + params![chat_id], )?; sql::execute( context, &context.sql, "DELETE FROM chats WHERE id=?;", - params![chat_id as i32], + params![chat_id], )?; context.call_cb(Event::MsgsChanged { msg_id: MsgId::new(0), - chat_id: 0, + chat_id: ChatId::new(0), }); job_kill_action(context, Action::Housekeeping); @@ -1471,11 +1616,11 @@ pub fn delete(context: &Context, chat_id: u32) -> Result<(), Error> { Ok(()) } -pub fn get_chat_contacts(context: &Context, chat_id: u32) -> Vec { +pub fn get_chat_contacts(context: &Context, chat_id: ChatId) -> Vec { /* Normal chats do not include SELF. Group chats do (as it may happen that one is deleted from a groupchat but the chats stays visible, moreover, this makes displaying lists easier) */ - if chat_id == DC_CHAT_ID_DEADDROP { + if chat_id.is_deaddrop() { return Vec::new(); } @@ -1485,9 +1630,12 @@ pub fn get_chat_contacts(context: &Context, chat_id: u32) -> Vec { context .sql .query_map( - "SELECT cc.contact_id FROM chats_contacts cc \ - LEFT JOIN contacts c ON c.id=cc.contact_id WHERE cc.chat_id=? \ - ORDER BY c.id=1, LOWER(c.name||c.addr), c.id;", + "SELECT cc.contact_id + FROM chats_contacts cc + LEFT JOIN contacts c + ON c.id=cc.contact_id + WHERE cc.chat_id=? + ORDER BY c.id=1, LOWER(c.name||c.addr), c.id;", params![chat_id], |row| row.get::<_, u32>(0), |ids| ids.collect::, _>>().map_err(Into::into), @@ -1499,7 +1647,7 @@ pub fn create_group_chat( context: &Context, verified: VerifiedStatus, chat_name: impl AsRef, -) -> Result { +) -> Result { ensure!(!chat_name.as_ref().is_empty(), "Invalid chat name"); let draft_txt = context.stock_string_repl_str(StockMessage::NewGroupDraft, &chat_name); @@ -1521,9 +1669,9 @@ pub fn create_group_chat( ], )?; - let chat_id = sql::get_rowid(context, &context.sql, "chats", "grpid", grpid); - - if chat_id != 0 { + let row_id = sql::get_rowid(context, &context.sql, "chats", "grpid", grpid); + let chat_id = ChatId::new(row_id); + if !chat_id.is_error() { if add_to_chat_contacts_table(context, chat_id, DC_CONTACT_ID_SELF) { let mut draft_msg = Message::new(Viewtype::Text); draft_msg.set_text(Some(draft_txt)); @@ -1532,7 +1680,7 @@ pub fn create_group_chat( context.call_cb(Event::MsgsChanged { msg_id: MsgId::new(0), - chat_id: 0, + chat_id: ChatId::new(0), }); } @@ -1541,20 +1689,20 @@ pub fn create_group_chat( /* you MUST NOT modify this or the following strings */ // Context functions to work with chats -pub fn add_to_chat_contacts_table(context: &Context, chat_id: u32, contact_id: u32) -> bool { +pub fn add_to_chat_contacts_table(context: &Context, chat_id: ChatId, contact_id: u32) -> bool { // add a contact to a chat; the function does not check the type or if any of the record exist or are already // added to the chat! sql::execute( context, &context.sql, "INSERT INTO chats_contacts (chat_id, contact_id) VALUES(?, ?)", - params![chat_id as i32, contact_id as i32], + params![chat_id, contact_id as i32], ) .is_ok() } /// Adds a contact to the chat. -pub fn add_contact_to_chat(context: &Context, chat_id: u32, contact_id: u32) -> bool { +pub fn add_contact_to_chat(context: &Context, chat_id: ChatId, contact_id: u32) -> bool { match add_contact_to_chat_ex(context, chat_id, contact_id, false) { Ok(res) => res, Err(err) => { @@ -1566,14 +1714,11 @@ pub fn add_contact_to_chat(context: &Context, chat_id: u32, contact_id: u32) -> pub(crate) fn add_contact_to_chat_ex( context: &Context, - chat_id: u32, + chat_id: ChatId, contact_id: u32, from_handshake: bool, ) -> Result { - ensure!( - chat_id > DC_CHAT_ID_LAST_SPECIAL, - "can not add member to special chats" - ); + ensure!(!chat_id.is_special(), "can not add member to special chats"); let contact = Contact::get_by_id(context, contact_id)?; let mut msg = Message::default(); @@ -1583,7 +1728,7 @@ pub(crate) fn add_contact_to_chat_ex( let mut chat = Chat::load_from_db(context, chat_id)?; ensure!( real_group_exists(context, chat_id), - "chat_id {} is not a group where one can add members", + "{} is not a group where one can add members", chat_id ); ensure!( @@ -1661,47 +1806,44 @@ pub(crate) fn add_contact_to_chat_ex( Ok(true) } -fn real_group_exists(context: &Context, chat_id: u32) -> bool { +fn real_group_exists(context: &Context, chat_id: ChatId) -> bool { // check if a group or a verified group exists under the given ID - if !context.sql.is_open() || chat_id <= DC_CHAT_ID_LAST_SPECIAL { + if !context.sql.is_open() || chat_id.is_special() { return false; } context .sql .exists( - "SELECT id FROM chats WHERE id=? AND (type=120 OR type=130);", - params![chat_id as i32], + "SELECT id FROM chats WHERE id=? AND (type=120 OR type=130);", + params![chat_id], ) .unwrap_or_default() } -pub fn reset_gossiped_timestamp(context: &Context, chat_id: u32) -> Result<(), Error> { +pub fn reset_gossiped_timestamp(context: &Context, chat_id: ChatId) -> Result<(), Error> { set_gossiped_timestamp(context, chat_id, 0) } /// Get timestamp of the last gossip sent in the chat. /// Zero return value means that gossip was never sent. -pub fn get_gossiped_timestamp(context: &Context, chat_id: u32) -> i64 { +pub fn get_gossiped_timestamp(context: &Context, chat_id: ChatId) -> i64 { context .sql .query_get_value::<_, i64>( context, "SELECT gossiped_timestamp FROM chats WHERE id=?;", - params![chat_id as i32], + params![chat_id], ) .unwrap_or_default() } pub fn set_gossiped_timestamp( context: &Context, - chat_id: u32, + chat_id: ChatId, timestamp: i64, ) -> Result<(), Error> { - ensure!( - chat_id > DC_CHAT_ID_LAST_SPECIAL, - "can not add member to special chats" - ); + ensure!(!chat_id.is_special(), "can not add member to special chats"); info!( context, "set gossiped_timestamp for chat #{} to {}.", chat_id, timestamp, @@ -1711,12 +1853,12 @@ pub fn set_gossiped_timestamp( context, &context.sql, "UPDATE chats SET gossiped_timestamp=? WHERE id=?;", - params![timestamp, chat_id as i32], + params![timestamp, chat_id], )?; Ok(()) } -pub fn shall_attach_selfavatar(context: &Context, chat_id: u32) -> Result { +pub fn shall_attach_selfavatar(context: &Context, chat_id: ChatId) -> Result { // versions before 12/2019 already allowed to set selfavatar, however, it was never sent to others. // to avoid sending out previously set selfavatars unexpectedly we added this additional check. // it can be removed after some time. @@ -1753,7 +1895,7 @@ pub fn shall_attach_selfavatar(context: &Context, chat_id: u32) -> Result Result<(), Error> { context.sql.execute( @@ -1767,12 +1909,12 @@ pub fn set_selfavatar_timestamp( pub fn remove_contact_from_chat( context: &Context, - chat_id: u32, + chat_id: ChatId, contact_id: u32, ) -> Result<(), Error> { ensure!( - chat_id > DC_CHAT_ID_LAST_SPECIAL, - "bad chat_id = {} <= DC_CHAT_ID_LAST_SPECIAL", + !chat_id.is_special(), + "bad chat_id, can not be special chat: {}", chat_id ); ensure!( @@ -1828,7 +1970,7 @@ pub fn remove_contact_from_chat( context, &context.sql, "DELETE FROM chats_contacts WHERE chat_id=? AND contact_id=?;", - params![chat_id as i32, contact_id as i32], + params![chat_id, contact_id as i32], ) .is_ok() { @@ -1871,14 +2013,14 @@ pub fn is_group_explicitly_left(context: &Context, grpid: impl AsRef) -> Re pub fn set_chat_name( context: &Context, - chat_id: u32, + chat_id: ChatId, new_name: impl AsRef, ) -> Result<(), Error> { /* the function only sets the names of group chats; normal chats get their names from the contacts */ let mut success = false; ensure!(!new_name.as_ref().is_empty(), "Invalid name"); - ensure!(chat_id > DC_CHAT_ID_LAST_SPECIAL, "Invalid chat ID"); + ensure!(!chat_id.is_special(), "Invalid chat ID"); let chat = Chat::load_from_db(context, chat_id)?; let mut msg = Message::default(); @@ -1897,7 +2039,7 @@ pub fn set_chat_name( context, &context.sql, "UPDATE chats SET name=? WHERE id=?;", - params![new_name.as_ref(), chat_id as i32], + params![new_name.as_ref(), chat_id], ) .is_ok() { @@ -1939,10 +2081,10 @@ pub fn set_chat_name( /// `new_image` parameter. pub fn set_chat_profile_image( context: &Context, - chat_id: u32, + chat_id: ChatId, new_image: impl AsRef, // XXX use PathBuf ) -> Result<(), Error> { - ensure!(chat_id > DC_CHAT_ID_LAST_SPECIAL, "Invalid chat ID"); + ensure!(!chat_id.is_special(), "Invalid chat ID"); let mut chat = Chat::load_from_db(context, chat_id)?; ensure!( real_group_exists(context, chat_id), @@ -2002,20 +2144,17 @@ pub fn set_chat_profile_image( Ok(()) } -pub fn forward_msgs(context: &Context, msg_ids: &[MsgId], chat_id: u32) -> Result<(), Error> { +pub fn forward_msgs(context: &Context, msg_ids: &[MsgId], chat_id: ChatId) -> Result<(), Error> { ensure!(!msg_ids.is_empty(), "empty msgs_ids: nothing to forward"); - ensure!( - chat_id > DC_CHAT_ID_LAST_SPECIAL, - "can not forward to special chat" - ); + ensure!(!chat_id.is_special(), "can not forward to special chat"); - let mut created_chats: Vec = Vec::new(); + let mut created_chats: Vec = Vec::new(); let mut created_msgs: Vec = Vec::new(); let mut curr_timestamp: i64; unarchive(context, chat_id)?; if let Ok(mut chat) = Chat::load_from_db(context, chat_id) { - ensure!(chat.can_send(), "cannot send to chat #{}", chat_id); + ensure!(chat.can_send(), "cannot send to {}", chat_id); curr_timestamp = dc_create_smeared_timestamps(context, msg_ids.len()); let ids = context.sql.query_map( format!( @@ -2084,13 +2223,13 @@ pub fn forward_msgs(context: &Context, msg_ids: &[MsgId], chat_id: u32) -> Resul Ok(()) } -pub fn get_chat_contact_cnt(context: &Context, chat_id: u32) -> usize { +pub fn get_chat_contact_cnt(context: &Context, chat_id: ChatId) -> usize { context .sql .query_get_value::<_, isize>( context, "SELECT COUNT(*) FROM chats_contacts WHERE chat_id=?;", - params![chat_id as i32], + params![chat_id], ) .unwrap_or_default() as usize } @@ -2114,12 +2253,12 @@ pub fn get_chat_cnt(context: &Context) -> usize { pub(crate) fn get_chat_id_by_grpid( context: &Context, grpid: impl AsRef, -) -> Result<(u32, bool, Blocked), sql::Error> { +) -> Result<(ChatId, bool, Blocked), sql::Error> { context.sql.query_row( "SELECT id, blocked, type FROM chats WHERE grpid=?;", params![grpid.as_ref()], |row| { - let chat_id = row.get(0)?; + let chat_id = row.get::<_, ChatId>(0)?; let b = row.get::<_, Option>(1)?.unwrap_or_default(); let v = row.get::<_, Option>(2)?.unwrap_or_default(); @@ -2140,7 +2279,7 @@ pub fn add_device_msg( label.is_some() || msg.is_some(), "device-messages need label, msg or both" ); - let mut chat_id = 0; + let mut chat_id = ChatId::new(0); let mut msg_id = MsgId::new_unset(); if let Some(label) = label { @@ -2224,13 +2363,13 @@ pub fn delete_and_reset_all_device_msgs(context: &Context) -> Result<(), Error> /// Adds an informational message to chat. /// /// For example, it can be a message showing that a member was added to a group. -pub fn add_info_msg(context: &Context, chat_id: u32, text: impl AsRef) { +pub fn add_info_msg(context: &Context, chat_id: ChatId, text: impl AsRef) { let rfc724_mid = dc_create_outgoing_rfc724_mid(None, "@device"); if context.sql.execute( "INSERT INTO msgs (chat_id,from_id,to_id, timestamp,type,state, txt,rfc724_mid) VALUES (?,?,?, ?,?,?, ?,?);", params![ - chat_id as i32, + chat_id, DC_CONTACT_ID_INFO, DC_CONTACT_ID_INFO, dc_create_smeared_timestamp(context), @@ -2300,7 +2439,7 @@ mod tests { #[test] fn test_get_draft_special_chat_id() { let t = dummy_context(); - let draft = get_draft(&t.ctx, DC_CHAT_ID_LAST_SPECIAL).unwrap(); + let draft = get_draft(&t.ctx, ChatId::new(DC_CHAT_ID_LAST_SPECIAL)).unwrap(); assert!(draft.is_none()); } @@ -2309,7 +2448,7 @@ mod tests { // This is a weird case, maybe this should be an error but we // do not get this info from the database currently. let t = dummy_context(); - let draft = get_draft(&t.ctx, 42).unwrap(); + let draft = get_draft(&t.ctx, ChatId::new(42)).unwrap(); assert!(draft.is_none()); } @@ -2340,7 +2479,7 @@ mod tests { let t = dummy_context(); let chat_id = create_by_contact_id(&t.ctx, DC_CONTACT_ID_SELF).unwrap(); assert_eq!(DC_CONTACT_ID_SELF, 1); - assert!(chat_id > DC_CHAT_ID_LAST_SPECIAL); + assert!(!chat_id.is_special()); let chat = Chat::load_from_db(&t.ctx, chat_id).unwrap(); assert_eq!(chat.id, chat_id); assert!(chat.is_self_talk()); @@ -2354,9 +2493,9 @@ mod tests { #[test] fn test_deaddrop_chat() { let t = dummy_context(); - let chat = Chat::load_from_db(&t.ctx, DC_CHAT_ID_DEADDROP).unwrap(); + let chat = Chat::load_from_db(&t.ctx, ChatId::new(DC_CHAT_ID_DEADDROP)).unwrap(); assert_eq!(DC_CHAT_ID_DEADDROP, 1); - assert_eq!(chat.id, DC_CHAT_ID_DEADDROP); + assert!(chat.id.is_deaddrop()); assert!(!chat.is_self_talk()); assert!(!chat.archived); assert!(!chat.is_device_talk()); @@ -2430,7 +2569,7 @@ mod tests { // check device chat let chat_id = msg1.chat_id; assert_eq!(get_msg_cnt(&t.ctx, chat_id), 1); - assert!(chat_id > DC_CHAT_ID_LAST_SPECIAL); + assert!(!chat_id.is_special()); let chat = Chat::load_from_db(&t.ctx, chat_id); assert!(chat.is_ok()); let chat = chat.unwrap(); @@ -2444,7 +2583,7 @@ mod tests { // delete device message, make sure it is not added again message::delete_msgs(&t.ctx, &[*msg1_id.as_ref().unwrap()]); let msg1 = message::Message::load_from_db(&t.ctx, *msg1_id.as_ref().unwrap()); - assert!(msg1.is_err() || msg1.unwrap().chat_id == DC_CHAT_ID_TRASH); + assert!(msg1.is_err() || msg1.unwrap().chat_id.is_trash()); let msg3_id = add_device_msg(&t.ctx, Some("any-label"), Some(&mut msg2)); assert!(msg3_id.is_ok()); assert!(msg2_id.as_ref().unwrap().is_unset()); @@ -2554,8 +2693,8 @@ mod tests { .unwrap() .chat_id; let chat_id2 = create_by_contact_id(&t.ctx, DC_CONTACT_ID_SELF).unwrap(); - assert!(chat_id1 > DC_CHAT_ID_LAST_SPECIAL); - assert!(chat_id2 > DC_CHAT_ID_LAST_SPECIAL); + assert!(!chat_id1.is_special()); + assert!(!chat_id2.is_special()); assert_eq!(get_chat_cnt(&t.ctx), 2); assert_eq!(chatlist_len(&t.ctx, 0), 2); assert_eq!(chatlist_len(&t.ctx, DC_GCL_NO_SPECIALS), 2); @@ -2616,11 +2755,7 @@ mod tests { assert_ne!(contact1, 0); let chat_id = create_by_contact_id(&context.ctx, contact1).unwrap(); - assert!( - chat_id > DC_CHAT_ID_LAST_SPECIAL, - "chat_id too small {}", - chat_id - ); + assert!(!chat_id.is_special(), "chat_id too small {}", chat_id); let chat = Chat::load_from_db(&context.ctx, chat_id).unwrap(); let chat2_id = create_by_contact_id(&context.ctx, contact1).unwrap(); diff --git a/src/chatlist.rs b/src/chatlist.rs index 5453d916e..82102b8f5 100644 --- a/src/chatlist.rs +++ b/src/chatlist.rs @@ -36,7 +36,7 @@ use crate::stock::StockMessage; #[derive(Debug)] pub struct Chatlist { /// Stores pairs of `chat_id, message_id` - ids: Vec<(u32, MsgId)>, + ids: Vec<(ChatId, MsgId)>, } impl Chatlist { @@ -91,7 +91,7 @@ impl Chatlist { let mut add_archived_link_item = false; let process_row = |row: &rusqlite::Row| { - let chat_id: u32 = row.get(0)?; + let chat_id: ChatId = row.get(0)?; let msg_id: MsgId = row.get(1).unwrap_or_default(); Ok((chat_id, msg_id)) }; @@ -211,7 +211,10 @@ impl Chatlist { )?; if 0 == listflags & DC_GCL_NO_SPECIALS { if let Some(last_deaddrop_fresh_msg_id) = get_last_deaddrop_fresh_msg(context) { - ids.insert(0, (DC_CHAT_ID_DEADDROP, last_deaddrop_fresh_msg_id)); + ids.insert( + 0, + (ChatId::new(DC_CHAT_ID_DEADDROP), last_deaddrop_fresh_msg_id), + ); } add_archived_link_item = true; } @@ -220,9 +223,9 @@ impl Chatlist { if add_archived_link_item && dc_get_archived_cnt(context) > 0 { if ids.is_empty() && 0 != listflags & DC_GCL_ADD_ALLDONE_HINT { - ids.push((DC_CHAT_ID_ALLDONE_HINT, MsgId::new(0))); + ids.push((ChatId::new(DC_CHAT_ID_ALLDONE_HINT), MsgId::new(0))); } - ids.push((DC_CHAT_ID_ARCHIVED_LINK, MsgId::new(0))); + ids.push((ChatId::new(DC_CHAT_ID_ARCHIVED_LINK), MsgId::new(0))); } Ok(Chatlist { ids }) @@ -241,9 +244,9 @@ impl Chatlist { /// Get a single chat ID of a chatlist. /// /// To get the message object from the message ID, use dc_get_chat(). - pub fn get_chat_id(&self, index: usize) -> u32 { + pub fn get_chat_id(&self, index: usize) -> ChatId { if index >= self.ids.len() { - return 0; + return ChatId::new(0); } self.ids[index].0 } @@ -307,7 +310,7 @@ impl Chatlist { None }; - if chat.id == DC_CHAT_ID_ARCHIVED_LINK { + if chat.id.is_archived_link() { ret.text2 = None; } else if lastmsg.is_none() || lastmsg.as_ref().unwrap().from_id == DC_CONTACT_ID_UNDEFINED { diff --git a/src/constants.rs b/src/constants.rs index 4db0ede5a..4f8754bbe 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -49,6 +49,8 @@ pub const DC_HANDSHAKE_CONTINUE_NORMAL_PROCESSING: i32 = 0x01; pub const DC_HANDSHAKE_STOP_NORMAL_PROCESSING: i32 = 0x02; pub const DC_HANDSHAKE_ADD_DELETE_JOB: i32 = 0x04; +pub(crate) const DC_FROM_HANDSHAKE: i32 = 0x01; + pub const DC_GCL_ARCHIVED_ONLY: usize = 0x01; pub const DC_GCL_NO_SPECIALS: usize = 0x02; pub const DC_GCL_ADD_ALLDONE_HINT: usize = 0x04; diff --git a/src/contact.rs b/src/contact.rs index 0be1e3769..b14cd1d86 100644 --- a/src/contact.rs +++ b/src/contact.rs @@ -7,6 +7,7 @@ use itertools::Itertools; use rusqlite; use crate::aheader::EncryptPreference; +use crate::chat::ChatId; use crate::config::Config; use crate::constants::*; use crate::context::Context; @@ -262,7 +263,7 @@ impl Contact { .is_ok() { context.call_cb(Event::MsgsChanged { - chat_id: 0, + chat_id: ChatId::new(0), msg_id: MsgId::new(0), }); } diff --git a/src/context.rs b/src/context.rs index 29a858c55..b5eb0f626 100644 --- a/src/context.rs +++ b/src/context.rs @@ -50,7 +50,7 @@ pub struct Context { #[debug_stub = "Callback"] cb: Box, pub os_name: Option, - pub cmdline_sel_chat_id: Arc>, + pub cmdline_sel_chat_id: Arc>, pub bob: Arc>, pub last_smeared_timestamp: RwLock, pub running_state: Arc>, @@ -116,7 +116,7 @@ impl Context { oauth2_critical: Arc::new(Mutex::new(())), bob: Arc::new(RwLock::new(Default::default())), last_smeared_timestamp: RwLock::new(0), - cmdline_sel_chat_id: Arc::new(RwLock::new(0)), + cmdline_sel_chat_id: Arc::new(RwLock::new(ChatId::new(0))), inbox_thread: Arc::new(RwLock::new(JobThread::new( "INBOX", "configured_inbox_folder", @@ -345,7 +345,7 @@ impl Context { } #[allow(non_snake_case)] - pub fn search_msgs(&self, chat_id: u32, query: impl AsRef) -> Vec { + pub fn search_msgs(&self, chat_id: ChatId, query: impl AsRef) -> Vec { let real_query = query.as_ref().trim(); if real_query.is_empty() { return Vec::new(); @@ -353,7 +353,7 @@ impl Context { let strLikeInText = format!("%{}%", real_query); let strLikeBeg = format!("{}%", real_query); - let query = if 0 != chat_id { + let query = if !chat_id.is_unset() { concat!( "SELECT m.id AS id, m.timestamp AS timestamp", " FROM msgs m", @@ -385,7 +385,7 @@ impl Context { self.sql .query_map( query, - params![chat_id as i32, &strLikeInText, &strLikeBeg], + params![chat_id, &strLikeInText, &strLikeBeg], |row| row.get::<_, MsgId>("id"), |rows| { let mut ret = Vec::new(); diff --git a/src/dc_receive_imf.rs b/src/dc_receive_imf.rs index bae57b07e..2b25c25b1 100644 --- a/src/dc_receive_imf.rs +++ b/src/dc_receive_imf.rs @@ -3,7 +3,7 @@ use sha2::{Digest, Sha256}; use num_traits::FromPrimitive; -use crate::chat::{self, Chat}; +use crate::chat::{self, Chat, ChatId}; use crate::config::Config; use crate::constants::*; use crate::contact::*; @@ -61,7 +61,7 @@ pub fn dc_receive_imf( ensure!(mime_parser.has_headers(), "No Headers Found"); // the function returns the number of created messages in the database - let mut chat_id = 0; + let mut chat_id = ChatId::new(0); let mut hidden = false; let mut needs_delete_job = false; @@ -74,17 +74,17 @@ pub fn dc_receive_imf( // helper method to handle early exit and memory cleanup let cleanup = |context: &Context, create_event_to_send: &Option, - created_db_entries: &Vec<(usize, MsgId)>| { + created_db_entries: &Vec<(ChatId, MsgId)>| { if let Some(create_event_to_send) = create_event_to_send { for (chat_id, msg_id) in created_db_entries { let event = match create_event_to_send { CreateEvent::MsgsChanged => Event::MsgsChanged { msg_id: *msg_id, - chat_id: *chat_id as u32, + chat_id: *chat_id, }, CreateEvent::IncomingMsg => Event::IncomingMsg { msg_id: *msg_id, - chat_id: *chat_id as u32, + chat_id: *chat_id, }, }; context.call_cb(event); @@ -263,11 +263,11 @@ fn add_parts( from_id: u32, from_id_blocked: bool, hidden: &mut bool, - chat_id: &mut u32, + chat_id: &mut ChatId, flags: u32, needs_delete_job: &mut bool, insert_msg_id: &mut MsgId, - created_db_entries: &mut Vec<(usize, MsgId)>, + created_db_entries: &mut Vec<(ChatId, MsgId)>, create_event_to_send: &mut Option, ) -> Result<()> { let mut state: MessageState; @@ -308,7 +308,7 @@ fn add_parts( { // this message is a classic email not a chat-message nor a reply to one if show_emails == ShowEmails::Off { - *chat_id = DC_CHAT_ID_TRASH; + *chat_id = ChatId::new(DC_CHAT_ID_TRASH); allow_creation = false } else if show_emails == ShowEmails::AcceptedContacts { allow_creation = false @@ -334,7 +334,7 @@ fn add_parts( if mime_parser.get(HeaderDef::SecureJoin).is_some() { // avoid discarding by show_emails setting msgrmsg = MessengerMessage::Yes; - *chat_id = 0; + *chat_id = ChatId::new(0); allow_creation = true; match handle_securejoin_handshake(context, mime_parser, from_id) { Ok(securejoin::HandshakeMessage::Done) => { @@ -364,12 +364,12 @@ fn add_parts( // 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 - if *chat_id == 0 { + if chat_id.is_unset() { // try to create a group // (groups appear automatically only if the _sender_ is known, see core issue #54) let create_blocked = - if 0 != test_normal_chat_id && test_normal_chat_id_blocked == Blocked::Not { + if !test_normal_chat_id.is_unset() && test_normal_chat_id_blocked == Blocked::Not { Blocked::Not } else { Blocked::Deaddrop @@ -385,21 +385,24 @@ fn add_parts( )?; *chat_id = new_chat_id; chat_id_blocked = new_chat_id_blocked; - if *chat_id != 0 && chat_id_blocked != Blocked::Not && create_blocked == Blocked::Not { + if !chat_id.is_unset() + && chat_id_blocked != Blocked::Not + && create_blocked == Blocked::Not + { chat::unblock(context, new_chat_id); chat_id_blocked = Blocked::Not; } } - if *chat_id == 0 { + if chat_id.is_unset() { // check if the message belongs to a mailing list if mime_parser.is_mailinglist_message() { - *chat_id = DC_CHAT_ID_TRASH; + *chat_id = ChatId::new(DC_CHAT_ID_TRASH); info!(context, "Message belongs to a mailing list and is ignored.",); } } - if *chat_id == 0 { + if chat_id.is_unset() { // try to create a normal chat let create_blocked = if from_id == to_id { Blocked::Not @@ -407,7 +410,7 @@ fn add_parts( Blocked::Deaddrop }; - if 0 != test_normal_chat_id { + if !test_normal_chat_id.is_unset() { *chat_id = test_normal_chat_id; chat_id_blocked = test_normal_chat_id_blocked; } else if allow_creation { @@ -417,7 +420,7 @@ fn add_parts( *chat_id = id; chat_id_blocked = bl; } - if 0 != *chat_id && Blocked::Not != chat_id_blocked { + if !chat_id.is_unset() && Blocked::Not != chat_id_blocked { if Blocked::Not == create_blocked { chat::unblock(context, *chat_id); chat_id_blocked = Blocked::Not; @@ -435,9 +438,9 @@ fn add_parts( } } } - if *chat_id == 0 { + if chat_id.is_unset() { // maybe from_id is null or sth. else is suspicious, move message to trash - *chat_id = DC_CHAT_ID_TRASH; + *chat_id = ChatId::new(DC_CHAT_ID_TRASH); } // if the chat_id is blocked, @@ -459,7 +462,7 @@ fn add_parts( state = MessageState::OutDelivered; to_id = to_ids.get_index(0).cloned().unwrap_or_default(); if !to_ids.is_empty() { - if *chat_id == 0 { + if chat_id.is_unset() { let (new_chat_id, new_chat_id_blocked) = create_or_lookup_group( context, &mut mime_parser, @@ -471,12 +474,12 @@ fn add_parts( *chat_id = new_chat_id; chat_id_blocked = new_chat_id_blocked; // automatically unblock chat when the user sends a message - if *chat_id != 0 && chat_id_blocked != Blocked::Not { + if !chat_id.is_unset() && chat_id_blocked != Blocked::Not { chat::unblock(context, new_chat_id); chat_id_blocked = Blocked::Not; } } - if *chat_id == 0 && allow_creation { + if chat_id.is_unset() && allow_creation { let create_blocked = if MessengerMessage::No != msgrmsg && !Contact::is_blocked_load(context, to_id) { @@ -489,7 +492,7 @@ fn add_parts( *chat_id = id; chat_id_blocked = bl; - if 0 != *chat_id + if !chat_id.is_unset() && Blocked::Not != chat_id_blocked && Blocked::Not == create_blocked { @@ -502,7 +505,7 @@ fn add_parts( && to_ids.len() == 1 && to_ids.contains(&DC_CONTACT_ID_SELF); - if *chat_id == 0 && self_sent { + if chat_id.is_unset() && self_sent { // from_id==to_id==DC_CONTACT_ID_SELF - this is a self-sent messages, // maybe an Autocrypt Setup Messag let (id, bl) = @@ -511,13 +514,13 @@ fn add_parts( *chat_id = id; chat_id_blocked = bl; - if 0 != *chat_id && Blocked::Not != chat_id_blocked { + if !chat_id.is_unset() && Blocked::Not != chat_id_blocked { chat::unblock(context, *chat_id); chat_id_blocked = Blocked::Not; } } - if *chat_id == 0 { - *chat_id = DC_CHAT_ID_TRASH; + if chat_id.is_unset() { + *chat_id = ChatId::new(DC_CHAT_ID_TRASH); } } // correct message_timestamp, it should not be used before, @@ -588,7 +591,7 @@ fn add_parts( rfc724_mid, server_folder.as_ref(), server_uid as i32, - *chat_id as i32, + *chat_id, from_id as i32, to_id as i32, sort_timestamp, @@ -616,7 +619,7 @@ fn add_parts( let row_id = sql::get_rowid_with_conn(context, conn, "msgs", "rfc724_mid", &rfc724_mid); *insert_msg_id = MsgId::new(row_id); - created_db_entries.push((*chat_id as usize, *insert_msg_id)); + created_db_entries.push((*chat_id, *insert_msg_id)); } Ok(()) }, @@ -628,7 +631,7 @@ fn add_parts( ); // check event to send - if *chat_id == DC_CHAT_ID_TRASH { + if chat_id.is_trash() { *create_event_to_send = None; } else if incoming && state == MessageState::InFresh { if from_id_blocked { @@ -646,12 +649,12 @@ fn add_parts( fn save_locations( context: &Context, mime_parser: &MimeMessage, - chat_id: u32, + chat_id: ChatId, from_id: u32, insert_msg_id: MsgId, hidden: bool, ) { - if chat_id <= DC_CHAT_ID_LAST_SPECIAL { + if chat_id.is_special() { return; } let mut location_id_written = false; @@ -699,7 +702,7 @@ fn save_locations( fn calc_timestamps( context: &Context, - chat_id: u32, + chat_id: ChatId, from_id: u32, message_timestamp: i64, is_fresh_msg: bool, @@ -717,7 +720,7 @@ fn calc_timestamps( let last_msg_time: Option = context.sql.query_get_value( context, "SELECT MAX(timestamp) FROM msgs WHERE chat_id=? and from_id!=? AND timestamp>=?", - params![chat_id as i32, from_id as i32, *sort_timestamp], + params![chat_id, from_id as i32, *sort_timestamp], ); if let Some(last_msg_time) = last_msg_time { if last_msg_time > 0 && *sort_timestamp <= last_msg_time { @@ -749,7 +752,7 @@ fn create_or_lookup_group( create_blocked: Blocked, from_id: u32, to_ids: &ContactIds, -) -> Result<(u32, Blocked)> { +) -> Result<(ChatId, Blocked)> { let mut chat_id_blocked = Blocked::Not; let mut recreate_member_list = false; let mut send_EVENT_CHAT_MODIFIED = false; @@ -867,9 +870,9 @@ fn create_or_lookup_group( set_better_msg(mime_parser, &better_msg); // check, if we have a chat with this group ID - let (mut chat_id, chat_id_verified, _blocked) = - chat::get_chat_id_by_grpid(context, &grpid).unwrap_or((0, false, Blocked::Not)); - if chat_id != 0 { + let (mut chat_id, chat_id_verified, _blocked) = chat::get_chat_id_by_grpid(context, &grpid) + .unwrap_or((ChatId::new(0), false, Blocked::Not)); + if !chat_id.is_error() { if chat_id_verified { if let Err(err) = check_verified_properties(context, mime_parser, from_id as u32, to_ids) @@ -897,7 +900,7 @@ fn create_or_lookup_group( .get_config(Config::ConfiguredAddr) .unwrap_or_default(); - if chat_id == 0 + if chat_id.is_error() && !mime_parser.is_mailinglist_message() && !grpid.is_empty() && grpname.is_some() @@ -923,7 +926,7 @@ fn create_or_lookup_group( if !allow_creation { info!(context, "creating group forbidden by caller"); - return Ok((0, Blocked::Not)); + return Ok((ChatId::new(0), Blocked::Not)); } chat_id = create_group_record( @@ -938,9 +941,9 @@ fn create_or_lookup_group( } // again, check chat_id - if chat_id <= DC_CHAT_ID_LAST_SPECIAL { + if chat_id.is_special() { return if group_explicitly_left { - Ok((DC_CHAT_ID_TRASH, chat_id_blocked)) + Ok((ChatId::new(DC_CHAT_ID_TRASH), chat_id_blocked)) } else { create_or_lookup_adhoc_group( context, @@ -980,7 +983,7 @@ fn create_or_lookup_group( context, &context.sql, "UPDATE chats SET name=? WHERE id=?;", - params![grpname, chat_id as i32], + params![grpname, chat_id], ) .is_ok() { @@ -1018,7 +1021,7 @@ fn create_or_lookup_group( context, &context.sql, "DELETE FROM chats_contacts WHERE chat_id=?;", - params![chat_id as i32], + params![chat_id], ) .ok(); if skip.is_none() || !addr_cmp(&self_addr, skip.unwrap()) { @@ -1066,7 +1069,7 @@ fn create_or_lookup_adhoc_group( create_blocked: Blocked, from_id: u32, to_ids: &ContactIds, -) -> Result<(u32, Blocked)> { +) -> Result<(ChatId, Blocked)> { // if we're here, no grpid was found, check if there is an existing // ad-hoc group matching the to-list or if we should and can create one // (we do not want to heuristically look at the likely mangled Subject) @@ -1078,7 +1081,7 @@ fn create_or_lookup_adhoc_group( context, "not creating ad-hoc group for mailing list message" ); - return Ok((0, Blocked::Not)); + return Ok((ChatId::new(0), Blocked::Not)); } let mut member_ids: Vec = to_ids.iter().copied().collect(); @@ -1091,7 +1094,7 @@ fn create_or_lookup_adhoc_group( if member_ids.len() < 3 { info!(context, "not creating ad-hoc group: too few contacts"); - return Ok((0, Blocked::Not)); + return Ok((ChatId::new(0), Blocked::Not)); } let chat_ids = search_chat_ids_by_contact_ids(context, &member_ids)?; @@ -1099,25 +1102,35 @@ fn create_or_lookup_adhoc_group( let chat_ids_str = join(chat_ids.iter().map(|x| x.to_string()), ","); let res = context.sql.query_row( format!( - "SELECT c.id, c.blocked FROM chats c \ - LEFT JOIN msgs m ON m.chat_id=c.id WHERE c.id IN({}) ORDER BY m.timestamp DESC, m.id DESC LIMIT 1;", + "SELECT c.id, + c.blocked + FROM chats c + LEFT JOIN msgs m + ON m.chat_id=c.id + WHERE c.id IN({}) + ORDER BY m.timestamp DESC, + m.id DESC + LIMIT 1;", chat_ids_str ), params![], |row| { - Ok((row.get::<_, i32>(0)?, row.get::<_, Option>(1)?.unwrap_or_default())) - } + Ok(( + row.get::<_, ChatId>(0)?, + row.get::<_, Option>(1)?.unwrap_or_default(), + )) + }, ); if let Ok((id, id_blocked)) = res { /* success, chat found */ - return Ok((id as u32, id_blocked)); + return Ok((id, id_blocked)); } } if !allow_creation { info!(context, "creating ad-hoc group prevented from caller"); - return Ok((0, Blocked::Not)); + return Ok((ChatId::new(0), Blocked::Not)); } // we do not check if the message is a reply to another group, this may result in @@ -1131,7 +1144,7 @@ fn create_or_lookup_adhoc_group( context, "failed to create ad-hoc grpid for {:?}", member_ids ); - return Ok((0, Blocked::Not)); + return Ok((ChatId::new(0), Blocked::Not)); } // use subject as initial chat name let grpname = mime_parser.get_subject().unwrap_or_else(|| { @@ -1139,7 +1152,7 @@ fn create_or_lookup_adhoc_group( }); // create group record - let new_chat_id = create_group_record( + let new_chat_id: ChatId = create_group_record( context, &grpid, grpname, @@ -1161,7 +1174,7 @@ fn create_group_record( grpname: impl AsRef, create_blocked: Blocked, create_verified: VerifiedStatus, -) -> u32 { +) -> ChatId { if sql::execute( context, &context.sql, @@ -1186,12 +1199,13 @@ fn create_group_record( grpname.as_ref(), grpid.as_ref() ); - return 0; + return ChatId::new(0); } - let chat_id = sql::get_rowid(context, &context.sql, "chats", "grpid", grpid.as_ref()); + let row_id = sql::get_rowid(context, &context.sql, "chats", "grpid", grpid.as_ref()); + let chat_id = ChatId::new(row_id); info!( context, - "Created group '{}' grpid={} as chat #{}", + "Created group '{}' grpid={} as {}", grpname.as_ref(), grpid.as_ref(), chat_id @@ -1246,7 +1260,7 @@ fn hex_hash(s: impl AsRef) -> String { fn search_chat_ids_by_contact_ids( context: &Context, unsorted_contact_ids: &[u32], -) -> Result> { +) -> Result> { /* searches chat_id's by the given contact IDs, may return zero, one or more chat_id's */ let mut contact_ids = Vec::with_capacity(23); let mut chat_ids = Vec::with_capacity(23); @@ -1263,19 +1277,19 @@ fn search_chat_ids_by_contact_ids( let contact_ids_str = join(contact_ids.iter().map(|x| x.to_string()), ","); context.sql.query_map( format!( - "SELECT DISTINCT cc.chat_id, cc.contact_id \ - FROM chats_contacts cc \ - LEFT JOIN chats c ON c.id=cc.chat_id \ - WHERE cc.chat_id IN(SELECT chat_id FROM chats_contacts WHERE contact_id IN({})) \ - AND c.type=120 \ - AND cc.contact_id!=1 \ - ORDER BY cc.chat_id, cc.contact_id;", // 1=DC_CONTACT_ID_SELF + "SELECT DISTINCT cc.chat_id, cc.contact_id + FROM chats_contacts cc + LEFT JOIN chats c ON c.id=cc.chat_id + WHERE cc.chat_id IN(SELECT chat_id FROM chats_contacts WHERE contact_id IN({})) + AND c.type=120 + AND cc.contact_id!=1 + ORDER BY cc.chat_id, cc.contact_id;", // 1=DC_CONTACT_ID_SELF contact_ids_str ), params![], - |row| Ok((row.get::<_, u32>(0)?, row.get::<_, u32>(1)?)), + |row| Ok((row.get::<_, ChatId>(0)?, row.get::<_, u32>(1)?)), |rows| { - let mut last_chat_id = 0; + let mut last_chat_id = ChatId::new(0); let mut matches = 0; let mut mismatches = 0; diff --git a/src/events.rs b/src/events.rs index d532aae74..37a0a4bc4 100644 --- a/src/events.rs +++ b/src/events.rs @@ -4,6 +4,7 @@ use std::path::PathBuf; use strum::EnumProperty; +use crate::chat::ChatId; use crate::message::MsgId; impl Event { @@ -107,36 +108,36 @@ pub enum Event { /// - Chats created, deleted or archived /// - A draft has been set #[strum(props(id = "2000"))] - MsgsChanged { chat_id: u32, msg_id: MsgId }, + MsgsChanged { chat_id: ChatId, msg_id: MsgId }, /// There is a fresh message. Typically, the user will show an notification /// when receiving this message. /// /// There is no extra #DC_EVENT_MSGS_CHANGED event send together with this event. #[strum(props(id = "2005"))] - IncomingMsg { chat_id: u32, msg_id: MsgId }, + IncomingMsg { chat_id: ChatId, msg_id: MsgId }, /// A single message is sent successfully. State changed from DC_STATE_OUT_PENDING to /// DC_STATE_OUT_DELIVERED, see dc_msg_get_state(). #[strum(props(id = "2010"))] - MsgDelivered { chat_id: u32, msg_id: MsgId }, + MsgDelivered { chat_id: ChatId, msg_id: MsgId }, /// A single message could not be sent. State changed from DC_STATE_OUT_PENDING or DC_STATE_OUT_DELIVERED to /// DC_STATE_OUT_FAILED, see dc_msg_get_state(). #[strum(props(id = "2012"))] - MsgFailed { chat_id: u32, msg_id: MsgId }, + MsgFailed { chat_id: ChatId, msg_id: MsgId }, /// A single message is read by the receiver. State changed from DC_STATE_OUT_DELIVERED to /// DC_STATE_OUT_MDN_RCVD, see dc_msg_get_state(). #[strum(props(id = "2015"))] - MsgRead { chat_id: u32, msg_id: MsgId }, + MsgRead { chat_id: ChatId, msg_id: MsgId }, /// Chat changed. The name or the image of a chat group was changed or members were added or removed. /// Or the verify state of a chat has changed. /// See dc_set_chat_name(), dc_set_chat_profile_image(), dc_add_contact_to_chat() /// and dc_remove_contact_from_chat(). #[strum(props(id = "2020"))] - ChatModified(u32), + ChatModified(ChatId), /// Contact(s) created, renamed, blocked or deleted. /// @@ -205,5 +206,5 @@ pub enum Event { /// @param data1 (int) chat_id /// @param data2 (int) contact_id #[strum(props(id = "2062"))] - SecurejoinMemberAdded { chat_id: u32, contact_id: u32 }, + SecurejoinMemberAdded { chat_id: ChatId, contact_id: u32 }, } diff --git a/src/job.rs b/src/job.rs index 6b45bb891..41bcd5ee4 100644 --- a/src/job.rs +++ b/src/job.rs @@ -12,7 +12,7 @@ use rand::{thread_rng, Rng}; use async_std::task; use crate::blob::BlobObject; -use crate::chat; +use crate::chat::{self, ChatId}; use crate::config::Config; use crate::configure::*; use crate::constants::*; @@ -764,7 +764,7 @@ pub fn job_action_exists(context: &Context, action: Action) -> bool { fn set_delivered(context: &Context, msg_id: MsgId) { message::update_msg_state(context, msg_id, MessageState::OutDelivered); - let chat_id: i32 = context + let chat_id: ChatId = context .sql .query_get_value( context, @@ -772,10 +772,7 @@ fn set_delivered(context: &Context, msg_id: MsgId) { params![msg_id], ) .unwrap_or_default(); - context.call_cb(Event::MsgDelivered { - chat_id: chat_id as u32, - msg_id, - }); + context.call_cb(Event::MsgDelivered { chat_id, msg_id }); } /* special case for DC_JOB_SEND_MSG_TO_SMTP */ diff --git a/src/location.rs b/src/location.rs index 5bf648e14..3c6876f44 100644 --- a/src/location.rs +++ b/src/location.rs @@ -4,7 +4,7 @@ use bitflags::bitflags; use quick_xml; use quick_xml::events::{BytesEnd, BytesStart, BytesText}; -use crate::chat; +use crate::chat::{self, ChatId}; use crate::config::Config; use crate::constants::*; use crate::context::*; @@ -28,7 +28,7 @@ pub struct Location { pub timestamp: i64, pub contact_id: u32, pub msg_id: u32, - pub chat_id: u32, + pub chat_id: ChatId, pub marker: Option, pub independent: u32, } @@ -193,9 +193,9 @@ impl Kml { } // location streaming -pub fn send_locations_to_chat(context: &Context, chat_id: u32, seconds: i64) { +pub fn send_locations_to_chat(context: &Context, chat_id: ChatId, seconds: i64) { let now = time(); - if !(seconds < 0 || chat_id <= DC_CHAT_ID_LAST_SPECIAL) { + if !(seconds < 0 || chat_id.is_special()) { let is_sending_locations_before = is_sending_locations_to_chat(context, chat_id); if sql::execute( context, @@ -207,7 +207,7 @@ pub fn send_locations_to_chat(context: &Context, chat_id: u32, seconds: i64) { params![ if 0 != seconds { now } else { 0 }, if 0 != seconds { now + seconds } else { 0 }, - chat_id as i32, + chat_id, ], ) .is_ok() @@ -229,7 +229,7 @@ pub fn send_locations_to_chat(context: &Context, chat_id: u32, seconds: i64) { job_add( context, job::Action::MaybeSendLocationsEnded, - chat_id as i32, + chat_id.to_u32() as i32, Params::new(), seconds + 1, ); @@ -251,12 +251,12 @@ fn schedule_MAYBE_SEND_LOCATIONS(context: &Context, force_schedule: bool) { }; } -pub fn is_sending_locations_to_chat(context: &Context, chat_id: u32) -> bool { +pub fn is_sending_locations_to_chat(context: &Context, chat_id: ChatId) -> bool { context .sql .exists( "SELECT id FROM chats WHERE (? OR id=?) AND locations_send_until>?;", - params![if chat_id == 0 { 1 } else { 0 }, chat_id as i32, time()], + params![if chat_id.is_unset() { 1 } else { 0 }, chat_id, time()], ) .unwrap_or_default() } @@ -302,7 +302,7 @@ pub fn set(context: &Context, latitude: f64, longitude: f64, accuracy: f64) -> b pub fn get_range( context: &Context, - chat_id: u32, + chat_id: ChatId, contact_id: u32, timestamp_from: i64, mut timestamp_to: i64, @@ -320,8 +320,8 @@ pub fn get_range( AND (l.independent=1 OR (l.timestamp>=? AND l.timestamp<=?)) \ ORDER BY l.timestamp DESC, l.id DESC, msg_id DESC;", params![ - if chat_id == 0 { 1 } else { 0 }, - chat_id as i32, + if chat_id.is_unset() { 1 } else { 0 }, + chat_id, if contact_id == 0 { 1 } else { 0 }, contact_id as i32, timestamp_from, @@ -371,7 +371,7 @@ pub fn delete_all(context: &Context) -> Result<(), Error> { Ok(()) } -pub fn get_kml(context: &Context, chat_id: u32) -> Result<(String, u32), Error> { +pub fn get_kml(context: &Context, chat_id: ChatId) -> Result<(String, u32), Error> { let mut last_added_location_id = 0; let self_addr = context @@ -380,7 +380,7 @@ pub fn get_kml(context: &Context, chat_id: u32) -> Result<(String, u32), Error> let (locations_send_begin, locations_send_until, locations_last_sent) = context.sql.query_row( "SELECT locations_send_begin, locations_send_until, locations_last_sent FROM chats WHERE id=?;", - params![chat_id as i32], |row| { + params![chat_id], |row| { let send_begin: i64 = row.get(0)?; let send_until: i64 = row.get(1)?; let last_sent: i64 = row.get(2)?; @@ -465,14 +465,14 @@ pub fn get_message_kml(timestamp: i64, latitude: f64, longitude: f64) -> String pub fn set_kml_sent_timestamp( context: &Context, - chat_id: u32, + chat_id: ChatId, timestamp: i64, ) -> Result<(), Error> { sql::execute( context, &context.sql, "UPDATE chats SET locations_last_sent=? WHERE id=?;", - params![timestamp, chat_id as i32], + params![timestamp, chat_id], )?; Ok(()) @@ -495,12 +495,12 @@ pub fn set_msg_location_id( pub fn save( context: &Context, - chat_id: u32, + chat_id: ChatId, contact_id: u32, locations: &[Location], independent: bool, ) -> Result { - ensure!(chat_id > DC_CHAT_ID_LAST_SPECIAL, "Invalid chat id"); + ensure!(!chat_id.is_special(), "Invalid chat id"); context .sql .prepare2( @@ -520,7 +520,7 @@ pub fn save( stmt_insert.execute(params![ location.timestamp, contact_id as i32, - chat_id as i32, + chat_id, location.latitude, location.longitude, location.accuracy, @@ -562,7 +562,7 @@ pub fn JobMaybeSendLocations(context: &Context, _job: &Job) -> job::Status { WHERE locations_send_until>?;", params![now], |row| { - let chat_id: i32 = row.get(0)?; + let chat_id: ChatId = row.get(0)?; let locations_send_begin: i64 = row.get(1)?; let locations_last_sent: i64 = row.get(2)?; continue_streaming = true; @@ -629,7 +629,7 @@ pub fn JobMaybeSendLocations(context: &Context, _job: &Job) -> job::Status { for (chat_id, mut msg) in msgs.into_iter() { // TODO: better error handling - chat::send_msg(context, chat_id as u32, &mut msg).unwrap_or_default(); + chat::send_msg(context, chat_id, &mut msg).unwrap_or_default(); } } if continue_streaming { @@ -644,11 +644,11 @@ pub fn JobMaybeSendLocationsEnded(context: &Context, job: &mut Job) -> job::Stat // the function checks, if location-streaming is really ended; // if so, a device-message is added if not yet done. - let chat_id = job.foreign_id; + let chat_id = ChatId::new(job.foreign_id); let (send_begin, send_until) = job_try!(context.sql.query_row( "SELECT locations_send_begin, locations_send_until FROM chats WHERE id=?", - params![chat_id as i32], + params![chat_id], |row| Ok((row.get::<_, i64>(0)?, row.get::<_, i64>(1)?)), )); @@ -660,7 +660,7 @@ pub fn JobMaybeSendLocationsEnded(context: &Context, job: &mut Job) -> job::Stat // not streaming, device-message already sent job_try!(context.sql.execute( "UPDATE chats SET locations_send_begin=0, locations_send_until=0 WHERE id=?", - params![chat_id as i32], + params![chat_id], )); let stock_str = context.stock_system_msg(StockMessage::MsgLocationDisabled, "", "", 0); diff --git a/src/message.rs b/src/message.rs index d38aa07c7..8915dda1b 100644 --- a/src/message.rs +++ b/src/message.rs @@ -5,7 +5,7 @@ use std::path::{Path, PathBuf}; use deltachat_derive::{FromSql, ToSql}; use failure::Fail; -use crate::chat::{self, Chat}; +use crate::chat::{self, Chat, ChatId}; use crate::constants::*; use crate::contact::*; use crate::context::*; @@ -176,7 +176,7 @@ pub struct Message { pub(crate) id: MsgId, pub(crate) from_id: u32, pub(crate) to_id: u32, - pub(crate) chat_id: u32, + pub(crate) chat_id: ChatId, pub(crate) viewtype: Viewtype, pub(crate) state: MessageState, pub(crate) hidden: bool, @@ -400,9 +400,9 @@ impl Message { self.from_id } - pub fn get_chat_id(&self) -> u32 { + pub fn get_chat_id(&self) -> ChatId { if self.chat_blocked != Blocked::Not { - 1 + ChatId::new(DC_CHAT_ID_DEADDROP) } else { self.chat_id } @@ -737,7 +737,7 @@ impl Lot { self.text1 = None; self.text1_meaning = Meaning::None; } else { - if chat.id == DC_CHAT_ID_DEADDROP { + if chat.id.is_deaddrop() { if let Some(contact) = contact { self.text1 = Some(contact.get_display_name().into()); } else { @@ -929,7 +929,7 @@ pub fn delete_msgs(context: &Context, msg_ids: &[MsgId]) { delete_poi_location(context, msg.location_id); } } - update_msg_chat_id(context, *msg_id, DC_CHAT_ID_TRASH); + update_msg_chat_id(context, *msg_id, ChatId::new(DC_CHAT_ID_TRASH)); job_add( context, Action::DeleteMsgOnImap, @@ -941,7 +941,7 @@ pub fn delete_msgs(context: &Context, msg_ids: &[MsgId]) { if !msg_ids.is_empty() { context.call_cb(Event::MsgsChanged { - chat_id: 0, + chat_id: ChatId::new(0), msg_id: MsgId::new(0), }); job_kill_action(context, Action::Housekeeping); @@ -949,12 +949,12 @@ pub fn delete_msgs(context: &Context, msg_ids: &[MsgId]) { }; } -fn update_msg_chat_id(context: &Context, msg_id: MsgId, chat_id: u32) -> bool { +fn update_msg_chat_id(context: &Context, msg_id: MsgId, chat_id: ChatId) -> bool { sql::execute( context, &context.sql, "UPDATE msgs SET chat_id=? WHERE id=?;", - params![chat_id as i32, msg_id], + params![chat_id, msg_id], ) .is_ok() } @@ -1033,7 +1033,7 @@ pub fn markseen_msgs(context: &Context, msg_ids: &[MsgId]) -> bool { if send_event { context.call_cb(Event::MsgsChanged { - chat_id: 0, + chat_id: ChatId::new(0), msg_id: MsgId::new(0), }); } @@ -1144,14 +1144,14 @@ pub fn exists(context: &Context, msg_id: MsgId) -> bool { return false; } - let chat_id: Option = context.sql.query_get_value( + let chat_id: Option = context.sql.query_get_value( context, "SELECT chat_id FROM msgs WHERE id=?;", params![msg_id], ); if let Some(chat_id) = chat_id { - chat_id != DC_CHAT_ID_TRASH + !chat_id.is_trash() } else { false } @@ -1189,7 +1189,7 @@ pub fn mdn_from_ext( from_id: u32, rfc724_mid: &str, timestamp_sent: i64, -) -> Option<(u32, MsgId)> { +) -> Option<(ChatId, MsgId)> { if from_id <= DC_MSG_ID_LAST_SPECIAL || rfc724_mid.is_empty() { return None; } @@ -1209,7 +1209,7 @@ pub fn mdn_from_ext( |row| { Ok(( row.get::<_, MsgId>("msg_id")?, - row.get::<_, u32>("chat_id")?, + row.get::<_, ChatId>("chat_id")?, row.get::<_, Chattype>("type")?, row.get::<_, MessageState>("state")?, )) diff --git a/src/mimefactory.rs b/src/mimefactory.rs index b0bb52a5b..258f07710 100644 --- a/src/mimefactory.rs +++ b/src/mimefactory.rs @@ -83,7 +83,7 @@ impl<'a, 'b> MimeFactory<'a, 'b> { FROM chats_contacts cc \ LEFT JOIN contacts c ON cc.contact_id=c.id \ WHERE cc.chat_id=? AND cc.contact_id>9;", - params![msg.chat_id as i32], + params![msg.chat_id], |row| { let authname: String = row.get(0)?; let addr: String = row.get(1)?; @@ -163,7 +163,7 @@ impl<'a, 'b> MimeFactory<'a, 'b> { msg: &'b Message, additional_msg_ids: Vec, ) -> Result { - ensure!(msg.chat_id > DC_CHAT_ID_LAST_SPECIAL, "Invalid chat id"); + ensure!(!msg.chat_id.is_special(), "Invalid chat id"); let contact = Contact::load_from_db(context, msg.from_id)?; @@ -613,7 +613,9 @@ impl<'a, 'b> MimeFactory<'a, 'b> { email_to_add.into(), )); } - if 0 != self.msg.param.get_int(Param::Arg2).unwrap_or_default() & 0x1 { + if 0 != self.msg.param.get_int(Param::Arg2).unwrap_or_default() + & DC_FROM_HANDSHAKE + { info!( context, "sending secure-join message \'{}\' >>>>>>>>>>>>>>>>>>>>>>>>>", diff --git a/src/securejoin.rs b/src/securejoin.rs index 6a77a843e..513a6a233 100644 --- a/src/securejoin.rs +++ b/src/securejoin.rs @@ -3,7 +3,7 @@ use percent_encoding::{utf8_percent_encode, AsciiSet, NON_ALPHANUMERIC}; use crate::aheader::EncryptPreference; -use crate::chat::{self, Chat}; +use crate::chat::{self, Chat, ChatId}; use crate::config::*; use crate::constants::*; use crate::contact::*; @@ -65,7 +65,7 @@ macro_rules! get_qr_attr { }; } -pub fn dc_get_securejoin_qr(context: &Context, group_chat_id: u32) -> Option { +pub fn dc_get_securejoin_qr(context: &Context, group_chat_id: ChatId) -> Option { /*======================================================= ==== Alice - the inviter side ==== ==== Step 1 in "Setup verified contact" protocol ==== @@ -101,7 +101,7 @@ pub fn dc_get_securejoin_qr(context: &Context, group_chat_id: u32) -> Option Option { None } -pub fn dc_join_securejoin(context: &Context, qr: &str) -> u32 { +pub fn dc_join_securejoin(context: &Context, qr: &str) -> ChatId { let cleanup = - |context: &Context, contact_chat_id: u32, ongoing_allocated: bool, join_vg: bool| { + |context: &Context, contact_chat_id: ChatId, ongoing_allocated: bool, join_vg: bool| { let mut bob = context.bob.write().unwrap(); bob.expects = 0; - let ret_chat_id = if bob.status == DC_BOB_SUCCESS { + let ret_chat_id: ChatId = if bob.status == DC_BOB_SUCCESS { if join_vg { chat::get_chat_id_by_grpid( context, bob.qr_scan.as_ref().unwrap().text2.as_ref().unwrap(), ) - .unwrap_or((0, false, Blocked::Not)) + .unwrap_or((ChatId::new(0), false, Blocked::Not)) .0 } else { contact_chat_id } } else { - 0 + ChatId::new(0) }; bob.qr_scan = None; if ongoing_allocated { context.free_ongoing(); } - ret_chat_id as u32 + ret_chat_id }; /*======================================================== @@ -175,7 +175,7 @@ pub fn dc_join_securejoin(context: &Context, qr: &str) -> u32 { ==== Step 2 in "Setup verified contact" protocol ===== ========================================================*/ - let mut contact_chat_id: u32 = 0; + let mut contact_chat_id = ChatId::new(0); let mut join_vg: bool = false; info!(context, "Requesting secure-join ...",); @@ -189,11 +189,13 @@ pub fn dc_join_securejoin(context: &Context, qr: &str) -> u32 { error!(context, "Unknown QR code.",); return cleanup(&context, contact_chat_id, true, join_vg); } - contact_chat_id = chat::create_by_contact_id(context, qr_scan.id).unwrap_or_default(); - if contact_chat_id == 0 { - error!(context, "Unknown contact.",); - return cleanup(&context, contact_chat_id, true, join_vg); - } + contact_chat_id = match chat::create_by_contact_id(context, qr_scan.id) { + Ok(chat_id) => chat_id, + Err(_) => { + error!(context, "Unknown contact."); + return cleanup(&context, contact_chat_id, true, join_vg); + } + }; if context.shall_stop_ongoing() { return cleanup(&context, contact_chat_id, true, join_vg); } @@ -264,7 +266,7 @@ pub fn dc_join_securejoin(context: &Context, qr: &str) -> u32 { fn send_handshake_msg( context: &Context, - contact_chat_id: u32, + contact_chat_id: ChatId, step: &str, param2: impl AsRef, fingerprint: Option, @@ -301,7 +303,7 @@ fn send_handshake_msg( chat::send_msg(context, contact_chat_id, &mut msg).unwrap_or_default(); } -fn chat_id_2_contact_id(context: &Context, contact_chat_id: u32) -> u32 { +fn chat_id_2_contact_id(context: &Context, contact_chat_id: ChatId) -> u32 { let contacts = chat::get_chat_contacts(context, contact_chat_id); if contacts.len() == 1 { contacts[0] @@ -313,7 +315,7 @@ fn chat_id_2_contact_id(context: &Context, contact_chat_id: u32) -> u32 { fn fingerprint_equals_sender( context: &Context, fingerprint: impl AsRef, - contact_chat_id: u32, + contact_chat_id: ChatId, ) -> bool { let contacts = chat::get_chat_contacts(context, contact_chat_id); @@ -648,7 +650,7 @@ pub(crate) fn handle_securejoin_handshake( // only after we have returned. It does not impact // the security invariants of secure-join however. let (_, is_verified_group, _) = chat::get_chat_id_by_grpid(context, &group_id) - .unwrap_or((0, false, Blocked::Not)); + .unwrap_or((ChatId::new(0), false, Blocked::Not)); // when joining a non-verified group // the vg-member-added message may be unencrypted // when not all group members have keys or prefer encryption. @@ -750,7 +752,7 @@ pub(crate) fn handle_securejoin_handshake( } } -fn secure_connection_established(context: &Context, contact_chat_id: u32) { +fn secure_connection_established(context: &Context, contact_chat_id: ChatId) { let contact_id: u32 = chat_id_2_contact_id(context, contact_chat_id); let contact = Contact::get_by_id(context, contact_id); let addr = if let Ok(ref contact) = contact { @@ -763,7 +765,11 @@ fn secure_connection_established(context: &Context, contact_chat_id: u32) { emit_event!(context, Event::ChatModified(contact_chat_id)); } -fn could_not_establish_secure_connection(context: &Context, contact_chat_id: u32, details: &str) { +fn could_not_establish_secure_connection( + context: &Context, + contact_chat_id: ChatId, + details: &str, +) { let contact_id = chat_id_2_contact_id(context, contact_chat_id); let contact = Contact::get_by_id(context, contact_id); let msg = context.stock_string_repl_str( diff --git a/src/token.rs b/src/token.rs index ec04230ce..11ae53f0c 100644 --- a/src/token.rs +++ b/src/token.rs @@ -6,6 +6,7 @@ use deltachat_derive::*; +use crate::chat::ChatId; use crate::context::Context; use crate::dc_tools::*; use crate::sql; @@ -27,28 +28,28 @@ impl Default for Namespace { /// Creates a new token and saves it into the database. /// Returns created token. -pub fn save(context: &Context, namespace: Namespace, foreign_id: u32) -> String { +pub fn save(context: &Context, namespace: Namespace, foreign_id: ChatId) -> String { // foreign_id may be 0 let token = dc_create_id(); sql::execute( context, &context.sql, "INSERT INTO tokens (namespc, foreign_id, token, timestamp) VALUES (?, ?, ?, ?);", - params![namespace, foreign_id as i32, &token, time()], + params![namespace, foreign_id, &token, time()], ) .ok(); token } -pub fn lookup(context: &Context, namespace: Namespace, foreign_id: u32) -> Option { +pub fn lookup(context: &Context, namespace: Namespace, foreign_id: ChatId) -> Option { context.sql.query_get_value::<_, String>( context, "SELECT token FROM tokens WHERE namespc=? AND foreign_id=?;", - params![namespace, foreign_id as i32], + params![namespace, foreign_id], ) } -pub fn lookup_or_new(context: &Context, namespace: Namespace, foreign_id: u32) -> String { +pub fn lookup_or_new(context: &Context, namespace: Namespace, foreign_id: ChatId) -> String { lookup(context, namespace, foreign_id).unwrap_or_else(|| save(context, namespace, foreign_id)) }