From 5678562ce227f059e0718d40620d3a8ce2d88841 Mon Sep 17 00:00:00 2001 From: Simon Laux Date: Sat, 8 Feb 2020 20:47:20 +0100 Subject: [PATCH 01/17] represent archivestate as enum before it was a boolean, even though it is a 3 state --- deltachat-ffi/src/lib.rs | 22 +++--- examples/repl/cmdline.rs | 48 +++++++++---- examples/repl/main.rs | 4 +- python/src/deltachat/chat.py | 8 ++- src/chat.rs | 136 +++++++++++++++++++++++++---------- src/chatlist.rs | 4 +- src/dc_receive_imf.rs | 6 +- 7 files changed, 161 insertions(+), 67 deletions(-) diff --git a/deltachat-ffi/src/lib.rs b/deltachat-ffi/src/lib.rs index 0a7b9e832..5839050ee 100644 --- a/deltachat-ffi/src/lib.rs +++ b/deltachat-ffi/src/lib.rs @@ -25,6 +25,7 @@ use std::time::{Duration, SystemTime}; use libc::uintptr_t; use num_traits::{FromPrimitive, ToPrimitive}; +use deltachat::chat::ArchiveState; use deltachat::chat::ChatId; use deltachat::chat::MuteDuration; use deltachat::constants::DC_MSG_ID_LAST_SPECIAL; @@ -1199,17 +1200,15 @@ pub unsafe extern "C" fn dc_archive_chat( return; } let ffi_context = &*context; - let archive = if archive == 0 { - false - } else if archive == 1 { - true - } else { - return; + let archive_state = match archive { + 2 => ArchiveState::Pinned, + 1 => ArchiveState::Archived, + _ => ArchiveState::Normal, }; ffi_context .with_inner(|ctx| { ChatId::new(chat_id) - .set_archived(ctx, archive) + .set_archive_state(ctx, archive_state) .log_err(ffi_context, "Failed archive chat") .unwrap_or(()) }) @@ -2460,7 +2459,14 @@ pub unsafe extern "C" fn dc_chat_get_archived(chat: *mut dc_chat_t) -> libc::c_i return 0; } let ffi_chat = &*chat; - ffi_chat.chat.is_archived() as libc::c_int + let ffi_context = &*ffi_chat.context; + ffi_context + .with_inner(|ctx| match ffi_chat.chat.get_id().get_archive_state(ctx) { + ArchiveState::Normal => 0, + ArchiveState::Archived => 1, + ArchiveState::Pinned => 2, + } as libc::c_int) + .unwrap_or_else(|_| 0) } #[no_mangle] diff --git a/examples/repl/cmdline.rs b/examples/repl/cmdline.rs index a788fda01..11aaa3fe0 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, ChatId}; +use deltachat::chat::{self, ArchiveState, Chat, ChatId}; use deltachat::chatlist::*; use deltachat::constants::*; use deltachat::contact::*; @@ -371,6 +371,8 @@ pub fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::Error> { listmedia\n\ archive \n\ unarchive \n\ + pin \n\ + unpin \n\ delchat \n\ ===========================Message commands==\n\ listmsgs \n\ @@ -511,24 +513,30 @@ pub fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::Error> { for i in (0..cnt).rev() { let chat = Chat::load_from_db(context, chatlist.get_chat_id(i))?; println!( - "{}#{}: {} [{} fresh]", + "{}#{}: {} [{} fresh] {}", chat_prefix(&chat), chat.get_id(), chat.get_name(), chat.get_id().get_fresh_msg_cnt(context), + match chat.get_id().get_archive_state(context) { + ArchiveState::Normal => "", + ArchiveState::Archived => "📦", + ArchiveState::Pinned => "📌" + }, ); let lot = chatlist.get_summary(context, i, Some(&chat)); - let statestr = if chat.is_archived() { - " [Archived]" - } else { - match lot.get_state() { - LotState::MsgOutPending => " o", - LotState::MsgOutDelivered => " √", - LotState::MsgOutMdnRcvd => " √√", - LotState::MsgOutFailed => " !!", - _ => "", - } - }; + let statestr = + if chat.get_id().get_archive_state(context) == ArchiveState::Archived { + " [Archived]" + } else { + match lot.get_state() { + LotState::MsgOutPending => " o", + LotState::MsgOutDelivered => " √", + LotState::MsgOutMdnRcvd => " √√", + LotState::MsgOutFailed => " !!", + _ => "", + } + }; let timestr = dc_timestamp_to_str(lot.get_timestamp()); let text1 = lot.get_text1(); let text2 = lot.get_text2(); @@ -842,10 +850,20 @@ pub fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::Error> { } print!("\n"); } - "archive" | "unarchive" => { + "archive" | "unarchive" | "pin" | "unpin" => { ensure!(!arg1.is_empty(), "Argument missing."); let chat_id = ChatId::new(arg1.parse()?); - chat_id.set_archived(context, arg0 == "archive")?; + chat_id.set_archive_state( + context, + match arg0 { + "archive" => ArchiveState::Archived, + "unarchive" | "unpin" => ArchiveState::Normal, + "pin" => ArchiveState::Pinned, + _ => { + panic!("Unexpected command (This should never happen)") + } + }, + )?; } "delchat" => { ensure!(!arg1.is_empty(), "Argument missing."); diff --git a/examples/repl/main.rs b/examples/repl/main.rs index 25a2ab166..87a0a9397 100644 --- a/examples/repl/main.rs +++ b/examples/repl/main.rs @@ -263,7 +263,7 @@ const DB_COMMANDS: [&str; 11] = [ "housekeeping", ]; -const CHAT_COMMANDS: [&str; 24] = [ +const CHAT_COMMANDS: [&str; 26] = [ "listchats", "listarchived", "chat", @@ -287,6 +287,8 @@ const CHAT_COMMANDS: [&str; 24] = [ "listmedia", "archive", "unarchive", + "pin", + "unpin", "delchat", ]; const MESSAGE_COMMANDS: [&str; 8] = [ diff --git a/python/src/deltachat/chat.py b/python/src/deltachat/chat.py index c2517f156..03b052b24 100644 --- a/python/src/deltachat/chat.py +++ b/python/src/deltachat/chat.py @@ -423,7 +423,13 @@ class Chat(object): """return True if this chat is archived. :returns: True if archived. """ - return lib.dc_chat_get_archived(self._dc_chat) + return lib.dc_chat_get_archived(self._dc_chat) == 1 + + def is_pinned(self): + """return True if this chat is pinned. + :returns: True if pinned. + """ + return lib.dc_chat_get_archived(self._dc_chat) == 2 def enable_sending_locations(self, seconds): """enable sending locations for this chat. diff --git a/src/chat.rs b/src/chat.rs index b4e7c81d8..e8e1611f3 100644 --- a/src/chat.rs +++ b/src/chat.rs @@ -137,47 +137,60 @@ impl ChatId { } /// Archives or unarchives a chat. - pub fn set_archived(self, context: &Context, new_archived: bool) -> Result<(), Error> { + pub fn set_archive_state( + self, + context: &Context, + new_archive_state: ArchiveState, + ) -> Result<(), Error> { ensure!( !self.is_special(), "bad chat_id, can not be special chat: {}", self ); - if new_archived { + let mut send_event = false; + + if new_archive_state == ArchiveState::Archived { sql::execute( context, &context.sql, "UPDATE msgs SET state=? WHERE chat_id=? AND state=?;", params![MessageState::InNoticed, self, MessageState::InFresh], )?; + send_event = true; } sql::execute( context, &context.sql, "UPDATE chats SET archived=? WHERE id=?;", - params![new_archived, self], + params![new_archive_state, self], )?; - context.call_cb(Event::MsgsChanged { - msg_id: MsgId::new(0), - chat_id: ChatId::new(0), - }); + if send_event { + context.call_cb(Event::MsgsChanged { + msg_id: MsgId::new(0), + chat_id: ChatId::new(0), + }); + } Ok(()) } - // note that unarchive() is not the same as set_archived(false) - - // eg. unarchive() does not send events as done for set_archived(false). - pub fn unarchive(self, context: &Context) -> Result<(), Error> { - sql::execute( + pub fn get_archive_state(self, context: &Context) -> ArchiveState { + if self.is_special() { + return ArchiveState::Normal; + } + if let Some(archive_state) = context.sql.query_get_value::<_, ArchiveState>( context, - &context.sql, - "UPDATE chats SET archived=0 WHERE id=?", + "SELECT archived FROM chats WHERE id=?;", params![self], - )?; - Ok(()) + ) { + archive_state + } else { + // if it failed return normal to be safe + ArchiveState::Normal + } } /// Deletes a chat. @@ -421,7 +434,6 @@ pub struct Chat { pub id: ChatId, pub typ: Chattype, pub name: String, - archived: bool, pub grpid: String, blocked: Blocked, pub param: Params, @@ -445,7 +457,6 @@ impl Chat { 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)?, mute_duration: row.get(7)?, @@ -652,11 +663,13 @@ impl Chat { Some(message) => message.text.unwrap_or_else(String::new), _ => String::new(), }; + let archive_state = self.id.get_archive_state(context); Ok(ChatInfo { id: self.id, type_: self.typ as u32, name: self.name.clone(), - archived: self.archived, + archived: archive_state == ArchiveState::Archived, + pinned: archive_state == ArchiveState::Pinned, param: self.param.to_string(), gossiped_timestamp: self.get_gossiped_timestamp(context), is_sending_locations: self.is_sending_locations, @@ -668,11 +681,6 @@ impl Chat { }) } - /// Returns true if the chat is archived. - pub fn is_archived(&self) -> bool { - self.archived - } - pub fn is_unpromoted(&self) -> bool { self.param.get_int(Param::Unpromoted).unwrap_or_default() == 1 } @@ -928,6 +936,42 @@ impl Chat { } } +#[derive(Debug, PartialEq, Clone)] +pub enum ArchiveState { + /// Neither archived or pinned + Normal = 0, + Archived = 1, + /// Pinned (formaly known as sticky) + Pinned = 2, +} + +impl rusqlite::types::ToSql for ArchiveState { + fn to_sql(&self) -> rusqlite::Result { + let duration = match &self { + ArchiveState::Normal => 0, + ArchiveState::Archived => 1, + ArchiveState::Pinned => 2, + }; + let val = rusqlite::types::Value::Integer(duration as i64); + let out = rusqlite::types::ToSqlOutput::Owned(val); + Ok(out) + } +} + +impl rusqlite::types::FromSql for ArchiveState { + fn column_result(value: rusqlite::types::ValueRef) -> rusqlite::types::FromSqlResult { + i64::column_result(value).and_then(|val| { + Ok({ + match val { + 2 => ArchiveState::Pinned, + 1 => ArchiveState::Archived, + _ => ArchiveState::Normal, + } + }) + }) + } +} + /// The current state of a chat. #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[non_exhaustive] @@ -950,6 +994,9 @@ pub struct ChatInfo { /// Whether the chat is archived. pub archived: bool, + /// Wether the chat is pinned. + pub pinned: bool, + /// The "params" of the chat. /// /// This is the string-serialised version of [Params] currently. @@ -1301,7 +1348,7 @@ fn prepare_msg_common( ) -> Result { msg.id = MsgId::new_unset(); prepare_msg_blob(context, msg)?; - chat_id.unarchive(context)?; + chat_id.set_archive_state(context, ArchiveState::Normal)?; let mut chat = Chat::load_from_db(context, chat_id)?; ensure!(chat.can_send(), "cannot send to {}", chat_id); @@ -2226,7 +2273,7 @@ pub fn forward_msgs(context: &Context, msg_ids: &[MsgId], chat_id: ChatId) -> Re let mut created_msgs: Vec = Vec::new(); let mut curr_timestamp: i64; - chat_id.unarchive(context)?; + chat_id.set_archive_state(context, ArchiveState::Normal)?; if let Ok(mut chat) = Chat::load_from_db(context, chat_id) { ensure!(chat.can_send(), "cannot send to {}", chat_id); curr_timestamp = dc_create_smeared_timestamps(context, msg_ids.len()); @@ -2369,7 +2416,7 @@ pub fn add_device_msg( let rfc724_mid = dc_create_outgoing_rfc724_mid(None, "@device"); msg.try_calc_and_set_dimensions(context).ok(); prepare_msg_blob(context, msg)?; - chat_id.unarchive(context)?; + chat_id.set_archive_state(context, ArchiveState::Normal)?; context.sql.execute( "INSERT INTO msgs (chat_id,from_id,to_id, timestamp,type,state, txt,param,rfc724_mid) \ @@ -2487,6 +2534,7 @@ mod tests { "type": 100, "name": "bob", "archived": false, + "pinned": false, "param": "", "gossiped_timestamp": 0, "is_sending_locations": false, @@ -2560,7 +2608,7 @@ mod tests { let chat = Chat::load_from_db(&t.ctx, chat_id).unwrap(); assert_eq!(chat.id, chat_id); assert!(chat.is_self_talk()); - assert!(!chat.archived); + assert!(chat_id.get_archive_state(&t.ctx) == ArchiveState::Normal); assert!(!chat.is_device_talk()); assert!(chat.can_send()); assert_eq!(chat.name, t.ctx.stock_str(StockMessage::SavedMessages)); @@ -2574,7 +2622,7 @@ mod tests { assert_eq!(DC_CHAT_ID_DEADDROP, 1); assert!(chat.id.is_deaddrop()); assert!(!chat.is_self_talk()); - assert!(!chat.archived); + assert!(chat.get_id().get_archive_state(&t.ctx) == ArchiveState::Normal); assert!(!chat.is_device_talk()); assert!(!chat.can_send()); assert_eq!(chat.name, t.ctx.stock_str(StockMessage::DeadDrop)); @@ -2780,29 +2828,39 @@ mod tests { assert_eq!(DC_GCL_NO_SPECIALS, 0x02); // archive first chat - assert!(chat_id1.set_archived(&t.ctx, true).is_ok()); - assert!(Chat::load_from_db(&t.ctx, chat_id1).unwrap().is_archived()); - assert!(!Chat::load_from_db(&t.ctx, chat_id2).unwrap().is_archived()); + assert!(chat_id1 + .set_archive_state(&t.ctx, ArchiveState::Archived) + .is_ok()); + assert!(chat_id1.get_archive_state(&t.ctx) == ArchiveState::Archived); + assert!(chat_id2.get_archive_state(&t.ctx) == ArchiveState::Normal); assert_eq!(get_chat_cnt(&t.ctx), 2); assert_eq!(chatlist_len(&t.ctx, 0), 2); // including DC_CHAT_ID_ARCHIVED_LINK now assert_eq!(chatlist_len(&t.ctx, DC_GCL_NO_SPECIALS), 1); assert_eq!(chatlist_len(&t.ctx, DC_GCL_ARCHIVED_ONLY), 1); // archive second chat - assert!(chat_id2.set_archived(&t.ctx, true).is_ok()); - assert!(Chat::load_from_db(&t.ctx, chat_id1).unwrap().is_archived()); - assert!(Chat::load_from_db(&t.ctx, chat_id2).unwrap().is_archived()); + assert!(chat_id2 + .set_archive_state(&t.ctx, ArchiveState::Archived) + .is_ok()); + assert!(chat_id1.get_archive_state(&t.ctx) == ArchiveState::Archived); + assert!(chat_id2.get_archive_state(&t.ctx) == ArchiveState::Archived); assert_eq!(get_chat_cnt(&t.ctx), 2); assert_eq!(chatlist_len(&t.ctx, 0), 1); // only DC_CHAT_ID_ARCHIVED_LINK now assert_eq!(chatlist_len(&t.ctx, DC_GCL_NO_SPECIALS), 0); assert_eq!(chatlist_len(&t.ctx, DC_GCL_ARCHIVED_ONLY), 2); // archive already archived first chat, unarchive second chat two times - assert!(chat_id1.set_archived(&t.ctx, true).is_ok()); - assert!(chat_id2.set_archived(&t.ctx, false).is_ok()); - assert!(chat_id2.set_archived(&t.ctx, false).is_ok()); - assert!(Chat::load_from_db(&t.ctx, chat_id1).unwrap().is_archived()); - assert!(!Chat::load_from_db(&t.ctx, chat_id2).unwrap().is_archived()); + assert!(chat_id1 + .set_archive_state(&t.ctx, ArchiveState::Archived) + .is_ok()); + assert!(chat_id2 + .set_archive_state(&t.ctx, ArchiveState::Normal) + .is_ok()); + assert!(chat_id2 + .set_archive_state(&t.ctx, ArchiveState::Normal) + .is_ok()); + assert!(chat_id1.get_archive_state(&t.ctx) == ArchiveState::Archived); + assert!(chat_id2.get_archive_state(&t.ctx) == ArchiveState::Normal); 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), 1); diff --git a/src/chatlist.rs b/src/chatlist.rs index 3c1b3df90..6cb0d1165 100644 --- a/src/chatlist.rs +++ b/src/chatlist.rs @@ -392,7 +392,9 @@ mod tests { let chats = Chatlist::try_load(&t.ctx, DC_GCL_ARCHIVED_ONLY, None, None).unwrap(); assert_eq!(chats.len(), 0); - chat_id1.set_archived(&t.ctx, true).ok(); + chat_id1 + .set_archive_state(&t.ctx, ArchiveState::Archived) + .ok(); let chats = Chatlist::try_load(&t.ctx, DC_GCL_ARCHIVED_ONLY, None, None).unwrap(); assert_eq!(chats.len(), 1); } diff --git a/src/dc_receive_imf.rs b/src/dc_receive_imf.rs index e0bb62fe8..36f6f8d9c 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, ChatId}; +use crate::chat::{self, ArchiveState, Chat, ChatId}; use crate::config::Config; use crate::constants::*; use crate::contact::*; @@ -548,7 +548,9 @@ fn add_parts( ); // unarchive chat - chat_id.unarchive(context)?; + if chat_id.get_archive_state(context) == ArchiveState::Archived { + chat_id.set_archive_state(context, ArchiveState::Normal)?; + } // if the mime-headers should be saved, find out its size // (the mime-header ends with an empty line) From 1765b8f2cfb94b98e757cca34de5ffd91dd09ff6 Mon Sep 17 00:00:00 2001 From: Simon Laux Date: Sat, 8 Feb 2020 21:16:40 +0100 Subject: [PATCH 02/17] show pinned chats again and order them to the top --- deltachat-ffi/src/lib.rs | 4 +--- examples/repl/cmdline.rs | 6 ++---- src/chat.rs | 10 +++++----- src/chatlist.rs | 4 ++-- 4 files changed, 10 insertions(+), 14 deletions(-) diff --git a/deltachat-ffi/src/lib.rs b/deltachat-ffi/src/lib.rs index 5839050ee..3b2446982 100644 --- a/deltachat-ffi/src/lib.rs +++ b/deltachat-ffi/src/lib.rs @@ -25,9 +25,7 @@ use std::time::{Duration, SystemTime}; use libc::uintptr_t; use num_traits::{FromPrimitive, ToPrimitive}; -use deltachat::chat::ArchiveState; -use deltachat::chat::ChatId; -use deltachat::chat::MuteDuration; +use deltachat::chat::{ArchiveState, ChatId, MuteDuration}; use deltachat::constants::DC_MSG_ID_LAST_SPECIAL; use deltachat::contact::Contact; use deltachat::context::Context; diff --git a/examples/repl/cmdline.rs b/examples/repl/cmdline.rs index 11aaa3fe0..f231bf9fe 100644 --- a/examples/repl/cmdline.rs +++ b/examples/repl/cmdline.rs @@ -521,7 +521,7 @@ pub fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::Error> { match chat.get_id().get_archive_state(context) { ArchiveState::Normal => "", ArchiveState::Archived => "📦", - ArchiveState::Pinned => "📌" + ArchiveState::Pinned => "📌", }, ); let lot = chatlist.get_summary(context, i, Some(&chat)); @@ -859,9 +859,7 @@ pub fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::Error> { "archive" => ArchiveState::Archived, "unarchive" | "unpin" => ArchiveState::Normal, "pin" => ArchiveState::Pinned, - _ => { - panic!("Unexpected command (This should never happen)") - } + _ => panic!("Unexpected command (This should never happen)"), }, )?; } diff --git a/src/chat.rs b/src/chat.rs index e8e1611f3..75c087bba 100644 --- a/src/chat.rs +++ b/src/chat.rs @@ -148,17 +148,17 @@ impl ChatId { self ); - let mut send_event = false; - - if new_archive_state == ArchiveState::Archived { + let send_event = if new_archive_state == ArchiveState::Archived { sql::execute( context, &context.sql, "UPDATE msgs SET state=? WHERE chat_id=? AND state=?;", params![MessageState::InNoticed, self, MessageState::InFresh], )?; - send_event = true; - } + true + } else { + false + }; sql::execute( context, diff --git a/src/chatlist.rs b/src/chatlist.rs index 6cb0d1165..0cabb6b5f 100644 --- a/src/chatlist.rs +++ b/src/chatlist.rs @@ -202,9 +202,9 @@ impl Chatlist { AND (hidden=0 OR state=?)) WHERE c.id>9 AND c.blocked=0 - AND c.archived=0 + AND NOT c.archived=1 GROUP BY c.id - ORDER BY IFNULL(m.timestamp,c.created_timestamp) DESC, m.id DESC;", + ORDER BY c.archived=2 DESC, IFNULL(m.timestamp,c.created_timestamp) DESC, m.id DESC;", params![MessageState::OutDraft], process_row, process_rows, From 5e5d45fb0a2349976feaf17e519c1f0c36ef90b3 Mon Sep 17 00:00:00 2001 From: Simon Laux Date: Sun, 9 Feb 2020 00:23:54 +0100 Subject: [PATCH 03/17] better fallbacks --- deltachat-ffi/src/lib.rs | 8 ++++++-- src/chat.rs | 6 +++++- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/deltachat-ffi/src/lib.rs b/deltachat-ffi/src/lib.rs index 3b2446982..a125037ef 100644 --- a/deltachat-ffi/src/lib.rs +++ b/deltachat-ffi/src/lib.rs @@ -1199,9 +1199,13 @@ pub unsafe extern "C" fn dc_archive_chat( } let ffi_context = &*context; let archive_state = match archive { - 2 => ArchiveState::Pinned, + 0 => ArchiveState::Normal, 1 => ArchiveState::Archived, - _ => ArchiveState::Normal, + 2 => ArchiveState::Pinned, + _ => { + eprintln!("ignoring careless call to dc_archive_chat(): unknown archived state"); + return; + } }; ffi_context .with_inner(|ctx| { diff --git a/src/chat.rs b/src/chat.rs index 75c087bba..8ff005822 100644 --- a/src/chat.rs +++ b/src/chat.rs @@ -965,7 +965,11 @@ impl rusqlite::types::FromSql for ArchiveState { match val { 2 => ArchiveState::Pinned, 1 => ArchiveState::Archived, - _ => ArchiveState::Normal, + 0 => ArchiveState::Normal, + _ => { + println!("unknown archived state, falling back to normal state (was this db opened with a newer deltachat version?)"); + ArchiveState::Normal + }, } }) }) From 23b6178e78b7324274d8a2de3cd6e419f4ab4f47 Mon Sep 17 00:00:00 2001 From: Simon Laux Date: Mon, 10 Feb 2020 16:09:53 +0100 Subject: [PATCH 04/17] add rust test for pin chat --- src/chat.rs | 51 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/src/chat.rs b/src/chat.rs index 8ff005822..3ce2713b6 100644 --- a/src/chat.rs +++ b/src/chat.rs @@ -2871,6 +2871,57 @@ mod tests { assert_eq!(chatlist_len(&t.ctx, DC_GCL_ARCHIVED_ONLY), 1); } + fn get_chats_from_chat_list(ctx: &Context, listflags: usize) -> Vec { + let chatlist = Chatlist::try_load(ctx, listflags, None, None).unwrap(); + let mut result = Vec::new(); + for chatlist_index in 0..chatlist.len() { + result.push(chatlist.get_chat_id(chatlist_index)) + } + result + } + + #[test] + fn test_pinned() { + let t = dummy_context(); + let mut msg = Message::new(Viewtype::Text); + msg.text = Some("foo".to_string()); + let msg_id = add_device_msg(&t.ctx, None, Some(&mut msg)).unwrap(); + let chat_id1 = message::Message::load_from_db(&t.ctx, msg_id) + .unwrap() + .chat_id; + let chat_id2 = create_by_contact_id(&t.ctx, DC_CONTACT_ID_SELF).unwrap(); + let chat_id3 = create_group_chat(&t.ctx, VerifiedStatus::Unverified, "foo").unwrap(); + + let chatlist = get_chats_from_chat_list(&t.ctx, DC_GCL_NO_SPECIALS); + assert_eq!(chatlist, vec![chat_id3, chat_id1, chat_id2]); + + // pin + assert!( + chat_id1 + .set_archive_state(&t.ctx, ArchiveState::Pinned) + .is_ok() + == true + ); + assert_eq!(chat_id1.get_archive_state(&t.ctx), ArchiveState::Pinned); + + // check if chat order changed + let chatlist = get_chats_from_chat_list(&t.ctx, DC_GCL_NO_SPECIALS); + assert_eq!(chatlist, vec![chat_id1, chat_id3, chat_id2]); + + // unpin + assert!( + chat_id1 + .set_archive_state(&t.ctx, ArchiveState::Normal) + .is_ok() + == true + ); + assert_eq!(chat_id1.get_archive_state(&t.ctx), ArchiveState::Normal); + + // check if chat order changed back + let chatlist = get_chats_from_chat_list(&t.ctx, DC_GCL_NO_SPECIALS); + assert_eq!(chatlist, vec![chat_id3, chat_id1, chat_id2]); + } + #[test] fn test_set_chat_name() { let t = dummy_context(); From ac4b2b9dfe99a9538d5ecd5c4f5a8a123d9d09b5 Mon Sep 17 00:00:00 2001 From: Simon Laux Date: Mon, 10 Feb 2020 20:03:43 +0100 Subject: [PATCH 05/17] python bindings for archive and py tests --- python/src/deltachat/chat.py | 22 +++++++++++++++++++++- python/tests/test_account.py | 11 +++++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/python/src/deltachat/chat.py b/python/src/deltachat/chat.py index 03b052b24..46a743571 100644 --- a/python/src/deltachat/chat.py +++ b/python/src/deltachat/chat.py @@ -424,13 +424,33 @@ class Chat(object): :returns: True if archived. """ return lib.dc_chat_get_archived(self._dc_chat) == 1 - + def is_pinned(self): """return True if this chat is pinned. :returns: True if pinned. """ return lib.dc_chat_get_archived(self._dc_chat) == 2 + def archive(self): + """archive the chat. + """ + lib.dc_archive_chat(self._dc_context, self.id, 1) + + def unarchive(self): + """unarchive the chat. + """ + lib.dc_archive_chat(self._dc_context, self.id, 0) + + def pin(self): + """pin the chat. + """ + lib.dc_archive_chat(self._dc_context, self.id, 2) + + def unpin(self): + """unpin the chat. (same as unarchive) + """ + self.unarchive() + def enable_sending_locations(self, seconds): """enable sending locations for this chat. diff --git a/python/tests/test_account.py b/python/tests/test_account.py index f31766e98..c06bde091 100644 --- a/python/tests/test_account.py +++ b/python/tests/test_account.py @@ -426,6 +426,17 @@ class TestOfflineChat: chat.remove_contact(contacts[3]) assert len(chat.get_contacts()) == 9 + def test_archive_and_pin_chat(self, ac1): + chat = ac1.create_group_chat(name="title1") + + assert not chat.is_archived() + chat.archive() + assert chat.is_archived() + chat.unarchive() + assert not chat.is_archived() + chat.pin() + assert chat.is_pinned() + class TestOnlineAccount: @pytest.mark.ignored From 2e8409f146e82776917aa743de7685ce58f5cfda Mon Sep 17 00:00:00 2001 From: Simon Laux Date: Tue, 11 Feb 2020 11:54:04 +0100 Subject: [PATCH 06/17] address some of flubs comments --- deltachat-ffi/deltachat.h | 4 ++++ deltachat-ffi/src/lib.rs | 2 +- python/src/deltachat/chat.py | 10 +++++----- python/src/deltachat/const.py | 3 +++ src/chat.rs | 18 ++++++++---------- 5 files changed, 21 insertions(+), 16 deletions(-) diff --git a/deltachat-ffi/deltachat.h b/deltachat-ffi/deltachat.h index a3f8f785a..040a3ec2b 100644 --- a/deltachat-ffi/deltachat.h +++ b/deltachat-ffi/deltachat.h @@ -2845,6 +2845,10 @@ char* dc_chat_get_profile_image (const dc_chat_t* chat); uint32_t dc_chat_get_color (const dc_chat_t* chat); +#define DC_CHAT_ARCHIVE_STATE_NORMAL 0 +#define DC_CHAT_ARCHIVE_STATE_ARCHIVED 1 +#define DC_CHAT_ARCHIVE_STATE_PINNED 2 + /** * Get archived state. * diff --git a/deltachat-ffi/src/lib.rs b/deltachat-ffi/src/lib.rs index a125037ef..fb514fe51 100644 --- a/deltachat-ffi/src/lib.rs +++ b/deltachat-ffi/src/lib.rs @@ -1203,7 +1203,7 @@ pub unsafe extern "C" fn dc_archive_chat( 1 => ArchiveState::Archived, 2 => ArchiveState::Pinned, _ => { - eprintln!("ignoring careless call to dc_archive_chat(): unknown archived state"); + ffi_context.warning("ignoring careless call to dc_archive_chat(): unknown archived state"); return; } }; diff --git a/python/src/deltachat/chat.py b/python/src/deltachat/chat.py index 46a743571..98b3220b5 100644 --- a/python/src/deltachat/chat.py +++ b/python/src/deltachat/chat.py @@ -423,28 +423,28 @@ class Chat(object): """return True if this chat is archived. :returns: True if archived. """ - return lib.dc_chat_get_archived(self._dc_chat) == 1 + return lib.dc_chat_get_archived(self._dc_chat) == const.DC_CHAT_ARCHIVE_STATE_ARCHIVED def is_pinned(self): """return True if this chat is pinned. :returns: True if pinned. """ - return lib.dc_chat_get_archived(self._dc_chat) == 2 + return lib.dc_chat_get_archived(self._dc_chat) == const.DC_CHAT_ARCHIVE_STATE_PINNED def archive(self): """archive the chat. """ - lib.dc_archive_chat(self._dc_context, self.id, 1) + lib.dc_archive_chat(self._dc_context, self.id, const.DC_CHAT_ARCHIVE_STATE_ARCHIVED) def unarchive(self): """unarchive the chat. """ - lib.dc_archive_chat(self._dc_context, self.id, 0) + lib.dc_archive_chat(self._dc_context, self.id, const.DC_CHAT_ARCHIVE_STATE_NORMAL) def pin(self): """pin the chat. """ - lib.dc_archive_chat(self._dc_context, self.id, 2) + lib.dc_archive_chat(self._dc_context, self.id, const.DC_CHAT_ARCHIVE_STATE_PINNED) def unpin(self): """unpin the chat. (same as unarchive) diff --git a/python/src/deltachat/const.py b/python/src/deltachat/const.py index baeaa2c11..0c898d6b7 100644 --- a/python/src/deltachat/const.py +++ b/python/src/deltachat/const.py @@ -33,6 +33,9 @@ DC_CHAT_TYPE_UNDEFINED = 0 DC_CHAT_TYPE_SINGLE = 100 DC_CHAT_TYPE_GROUP = 120 DC_CHAT_TYPE_VERIFIED_GROUP = 130 +DC_CHAT_ARCHIVE_STATE_NORMAL = 0 +DC_CHAT_ARCHIVE_STATE_ARCHIVED = 1 +DC_CHAT_ARCHIVE_STATE_PINNED = 2 DC_MSG_ID_MARKER1 = 1 DC_MSG_ID_DAYMARKER = 9 DC_MSG_ID_LAST_SPECIAL = 9 diff --git a/src/chat.rs b/src/chat.rs index 3ce2713b6..3e418d2c1 100644 --- a/src/chat.rs +++ b/src/chat.rs @@ -961,17 +961,15 @@ impl rusqlite::types::ToSql for ArchiveState { impl rusqlite::types::FromSql for ArchiveState { fn column_result(value: rusqlite::types::ValueRef) -> rusqlite::types::FromSqlResult { i64::column_result(value).and_then(|val| { - Ok({ - match val { - 2 => ArchiveState::Pinned, - 1 => ArchiveState::Archived, - 0 => ArchiveState::Normal, - _ => { - println!("unknown archived state, falling back to normal state (was this db opened with a newer deltachat version?)"); - ArchiveState::Normal - }, + match val { + 2 => Ok(ArchiveState::Pinned), + 1 => Ok(ArchiveState::Archived), + 0 => Ok(ArchiveState::Normal), + n => { + // unknown archived state, falling back to normal state (was this db opened with a newer deltachat version?) + Err(rusqlite::types::FromSqlError::OutOfRange(n)) } - }) + } }) } } From 6efe8e7d7cf8b3f057451a1d076073e31814c5eb Mon Sep 17 00:00:00 2001 From: Simon Laux Date: Wed, 12 Feb 2020 11:08:43 +0100 Subject: [PATCH 07/17] change ChatInfo.archived to tri-state and add to the changelog --- CHANGELOG.md | 8 ++++++++ deltachat-ffi/deltachat.h | 4 ++++ deltachat-ffi/src/lib.rs | 3 ++- src/chat.rs | 15 +++++++-------- 4 files changed, 21 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f3efb271c..7533d3b29 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +## Unreleased + +- pinned chats #1248 + +#### Api Changes + + - ChatInfo.archived is now a tri-state + ## 1.0.0-beta.24 - fix oauth2/gmail bug introduced in beta23 (not used in releases) #1219 diff --git a/deltachat-ffi/deltachat.h b/deltachat-ffi/deltachat.h index 040a3ec2b..12ac1b470 100644 --- a/deltachat-ffi/deltachat.h +++ b/deltachat-ffi/deltachat.h @@ -2700,6 +2700,10 @@ dc_context_t* dc_chatlist_get_context (dc_chatlist_t* chatlist); * * id: chat id * name: chat/group name + * archived: archived state can be one of: + * DC_CHAT_ARCHIVE_STATE_NORMAL + * DC_CHAT_ARCHIVE_STATE_ARCHIVED + * DC_CHAT_ARCHIVE_STATE_PINNED * color: color of this chat * last-message-from: who sent the last message * last-message-text: message (truncated) diff --git a/deltachat-ffi/src/lib.rs b/deltachat-ffi/src/lib.rs index fb514fe51..cd6502d63 100644 --- a/deltachat-ffi/src/lib.rs +++ b/deltachat-ffi/src/lib.rs @@ -1203,7 +1203,8 @@ pub unsafe extern "C" fn dc_archive_chat( 1 => ArchiveState::Archived, 2 => ArchiveState::Pinned, _ => { - ffi_context.warning("ignoring careless call to dc_archive_chat(): unknown archived state"); + ffi_context + .warning("ignoring careless call to dc_archive_chat(): unknown archived state"); return; } }; diff --git a/src/chat.rs b/src/chat.rs index 3e418d2c1..1e54893b9 100644 --- a/src/chat.rs +++ b/src/chat.rs @@ -668,8 +668,11 @@ impl Chat { id: self.id, type_: self.typ as u32, name: self.name.clone(), - archived: archive_state == ArchiveState::Archived, - pinned: archive_state == ArchiveState::Pinned, + archived: match archive_state { + ArchiveState::Normal => 0, + ArchiveState::Archived => 1, + ArchiveState::Pinned => 2, + }, param: self.param.to_string(), gossiped_timestamp: self.get_gossiped_timestamp(context), is_sending_locations: self.is_sending_locations, @@ -994,10 +997,7 @@ pub struct ChatInfo { pub name: String, /// Whether the chat is archived. - pub archived: bool, - - /// Wether the chat is pinned. - pub pinned: bool, + pub archived: u8, /// The "params" of the chat. /// @@ -2535,8 +2535,7 @@ mod tests { "id": 10, "type": 100, "name": "bob", - "archived": false, - "pinned": false, + "archived": 0, "param": "", "gossiped_timestamp": 0, "is_sending_locations": false, From 60493d30f68ce7b58621cad24a62b72584a71168 Mon Sep 17 00:00:00 2001 From: "B. Petersen" Date: Wed, 12 Feb 2020 20:05:57 +0100 Subject: [PATCH 08/17] target comment from @dignifiedquire, use ArchiveState inside core --- src/chat.rs | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/src/chat.rs b/src/chat.rs index 1e54893b9..9e7cb847f 100644 --- a/src/chat.rs +++ b/src/chat.rs @@ -668,11 +668,7 @@ impl Chat { id: self.id, type_: self.typ as u32, name: self.name.clone(), - archived: match archive_state { - ArchiveState::Normal => 0, - ArchiveState::Archived => 1, - ArchiveState::Pinned => 2, - }, + archived: archive_state, param: self.param.to_string(), gossiped_timestamp: self.get_gossiped_timestamp(context), is_sending_locations: self.is_sending_locations, @@ -939,7 +935,7 @@ impl Chat { } } -#[derive(Debug, PartialEq, Clone)] +#[derive(Debug, Eq, PartialEq, Clone, Serialize, Deserialize)] pub enum ArchiveState { /// Neither archived or pinned Normal = 0, @@ -997,7 +993,7 @@ pub struct ChatInfo { pub name: String, /// Whether the chat is archived. - pub archived: u8, + pub archived: ArchiveState, /// The "params" of the chat. /// From e3420da60f7acfc629db6b968b11997cfc0e143f Mon Sep 17 00:00:00 2001 From: "B. Petersen" Date: Thu, 13 Feb 2020 17:41:46 +0100 Subject: [PATCH 09/17] reword ffi from 'archived' to 'visibility' --- deltachat-ffi/deltachat.h | 91 ++++++++++++++++++++++++--------------- deltachat-ffi/src/lib.rs | 10 ++--- src/chatlist.rs | 2 +- 3 files changed, 63 insertions(+), 40 deletions(-) diff --git a/deltachat-ffi/deltachat.h b/deltachat-ffi/deltachat.h index 12ac1b470..aa3bd47cb 100644 --- a/deltachat-ffi/deltachat.h +++ b/deltachat-ffi/deltachat.h @@ -918,7 +918,7 @@ int dc_preconfigure_keypair (dc_context_t* context, const cha * or "Not now". * The UI can also offer a "Close" button that calls dc_marknoticed_contact() then. * - DC_CHAT_ID_ARCHIVED_LINK (6) - this special chat is present if the user has - * archived _any_ chat using dc_archive_chat(). The UI should show a link as + * archived _any_ chat using dc_set_chat_visibility(). The UI should show a link as * "Show archived chats", if the user clicks this item, the UI should show a * list of all archived chats that can be created by this function hen using * the DC_GCL_ARCHIVED_ONLY flag. @@ -1372,25 +1372,18 @@ uint32_t dc_get_next_media (dc_context_t* context, uint32_t ms /** - * Archive or unarchive a chat. + * Set chat visibility to pinned, archived or normal. * - * Archived chats are not included in the default chatlist returned - * by dc_get_chatlist(). Instead, if there are _any_ archived chats, - * the pseudo-chat with the chat_id DC_CHAT_ID_ARCHIVED_LINK will be added the the - * end of the chatlist. - * - * - To get a list of archived chats, use dc_get_chatlist() with the flag DC_GCL_ARCHIVED_ONLY. - * - To find out the archived state of a given chat, use dc_chat_get_archived() - * - Messages in archived chats are marked as being noticed, so they do not count as "fresh" - * - Calling this function usually results in the event #DC_EVENT_MSGS_CHANGED + * Calling this function usually results in the event #DC_EVENT_MSGS_CHANGED + * See @ref DC_CHAT_VISIBILITY for detailed information about the visibilities. * * @memberof dc_context_t * @param context The context object as returned from dc_context_new(). - * @param chat_id The ID of the chat to archive or unarchive. - * @param archive 1=archive chat, 0=unarchive chat, all other values are reserved for future use + * @param chat_id The ID of the chat to change the visibility for. + * @param visibility one of @ref DC_CHAT_VISIBILITY * @return None. */ -void dc_archive_chat (dc_context_t* context, uint32_t chat_id, int archive); +void dc_set_chat_visibility (dc_context_t* context, uint32_t chat_id, int visibility); /** @@ -2700,10 +2693,7 @@ dc_context_t* dc_chatlist_get_context (dc_chatlist_t* chatlist); * * id: chat id * name: chat/group name - * archived: archived state can be one of: - * DC_CHAT_ARCHIVE_STATE_NORMAL - * DC_CHAT_ARCHIVE_STATE_ARCHIVED - * DC_CHAT_ARCHIVE_STATE_PINNED + * visibility: one of @ref DC_CHAT_VISIBILITY * color: color of this chat * last-message-from: who sent the last message * last-message-text: message (truncated) @@ -2849,26 +2839,15 @@ char* dc_chat_get_profile_image (const dc_chat_t* chat); uint32_t dc_chat_get_color (const dc_chat_t* chat); -#define DC_CHAT_ARCHIVE_STATE_NORMAL 0 -#define DC_CHAT_ARCHIVE_STATE_ARCHIVED 1 -#define DC_CHAT_ARCHIVE_STATE_PINNED 2 - /** - * Get archived state. - * - * - 0 = normal chat, not archived, not sticky. - * - 1 = chat archived - * - 2 = chat sticky (reserved for future use, if you do not support this value, just treat the chat as a normal one) - * - * To archive or unarchive chats, use dc_archive_chat(). - * If chats are archived, this should be shown in the UI by a little icon or text, - * eg. the search will also return archived chats. + * Get visibility of chat. + * See @ref DC_CHAT_VISIBILITY for detailed information about the visibilities. * * @memberof dc_chat_t * @param chat The chat object. - * @return Archived state. + * @return One of @ref DC_CHAT_VISIBILITY */ -int dc_chat_get_archived (const dc_chat_t* chat); +int dc_chat_get_visibility (const dc_chat_t* chat); /** @@ -3781,7 +3760,7 @@ int dc_contact_is_verified (dc_contact_t* contact); * accessor functions. If no provider info is found, NULL will be * returned. */ -dc_provider_t* dc_provider_new_from_email (const dc_context_t*, const char* email); +dc_provider_t* dc_provider_new_from_email (const dc_context_t* context, const char* email); /** @@ -4540,6 +4519,8 @@ int64_t dc_lot_get_timestamp (const dc_lot_t* lot); #define DC_EVENT_RETURNS_STRING(e) ((e)==DC_EVENT_GET_STRING) // not used anymore char* dc_get_version_str (void); // deprecated void dc_array_add_id (dc_array_t*, uint32_t); // deprecated +#define dc_archive_chat(a,b,c) dc_set_chat_visibility((a), (b), (c)? 1 : 0) // not used anymore +#define dc_chat_get_archived(a) (dc_chat_get_visibility((a))==1? 1 : 0) // not used anymore /* @@ -4605,6 +4586,48 @@ void dc_array_add_id (dc_array_t*, uint32_t); // depreca */ +/** + * @defgroup DC_CHAT_VISIBILITY DC_CHAT_VISIBILITY + * + * These constants describe the visibility of a chat. + * The chat visibiliry can be get using dc_chat_get_visibility() + * and set using dc_set_chat_visibility(). + * + * @addtogroup DC_CHAT_VISIBILITY + * @{ + */ + +/** + * Chats with normal visibility are not archived and are shown below all pinned chats. + * Archived chats, that receive new messages automatically become normal chats. + */ +#define DC_CHAT_VISIBILITY_NORMAL 0 + +/** + * Archived chats are not included in the default chatlist returned by dc_get_chatlist(). + * Instead, if there are _any_ archived chats, the pseudo-chat + * with the chat_id DC_CHAT_ID_ARCHIVED_LINK will be added the the end of the chatlist. + * + * The UI typically shows a little icon or chats beside archived chats in the chatlist, + * this is needed as eg. the search will also return archived chats. + * + * If archived chats receive new messages, they become normal chats again. + * + * To get a list of archived chats, use dc_get_chatlist() with the flag DC_GCL_ARCHIVED_ONLY. + */ +#define DC_CHAT_VISIBILITY_ARCHIVED 1 + +/** + * Pinned chats are included in the default chatlist. moreover, + * they are always the first items, whether they have fresh messages or not. + */ +#define DC_CHAT_VISIBILITY_PINNED 2 + +/** + * @} + */ + + /* * TODO: Strings need some doumentation about used placeholders. * diff --git a/deltachat-ffi/src/lib.rs b/deltachat-ffi/src/lib.rs index cd6502d63..cfba118c1 100644 --- a/deltachat-ffi/src/lib.rs +++ b/deltachat-ffi/src/lib.rs @@ -1188,13 +1188,13 @@ pub unsafe extern "C" fn dc_get_next_media( } #[no_mangle] -pub unsafe extern "C" fn dc_archive_chat( +pub unsafe extern "C" fn dc_set_chat_visibility( context: *mut dc_context_t, chat_id: u32, archive: libc::c_int, ) { if context.is_null() { - eprintln!("ignoring careless call to dc_archive_chat()"); + eprintln!("ignoring careless call to dc_set_chat_visibility()"); return; } let ffi_context = &*context; @@ -1204,7 +1204,7 @@ pub unsafe extern "C" fn dc_archive_chat( 2 => ArchiveState::Pinned, _ => { ffi_context - .warning("ignoring careless call to dc_archive_chat(): unknown archived state"); + .warning("ignoring careless call to dc_set_chat_visibility(): unknown archived state"); return; } }; @@ -2456,9 +2456,9 @@ pub unsafe extern "C" fn dc_chat_get_color(chat: *mut dc_chat_t) -> u32 { } #[no_mangle] -pub unsafe extern "C" fn dc_chat_get_archived(chat: *mut dc_chat_t) -> libc::c_int { +pub unsafe extern "C" fn dc_chat_get_visibility(chat: *mut dc_chat_t) -> libc::c_int { if chat.is_null() { - eprintln!("ignoring careless call to dc_chat_get_archived()"); + eprintln!("ignoring careless call to dc_chat_get_visibility()"); return 0; } let ffi_chat = &*chat; diff --git a/src/chatlist.rs b/src/chatlist.rs index 0cabb6b5f..297883ab7 100644 --- a/src/chatlist.rs +++ b/src/chatlist.rs @@ -60,7 +60,7 @@ impl Chatlist { /// or "Not now". /// The UI can also offer a "Close" button that calls dc_marknoticed_contact() then. /// - DC_CHAT_ID_ARCHIVED_LINK (6) - this special chat is present if the user has - /// archived *any* chat using dc_archive_chat(). The UI should show a link as + /// archived *any* chat using dc_set_chat_visibility(). The UI should show a link as /// "Show archived chats", if the user clicks this item, the UI should show a /// list of all archived chats that can be created by this function hen using /// the DC_GCL_ARCHIVED_ONLY flag. From 2813e01e61d4db7063b8411c9283c6f315a08c0e Mon Sep 17 00:00:00 2001 From: "B. Petersen" Date: Thu, 13 Feb 2020 18:48:08 +0100 Subject: [PATCH 10/17] remove unneeded sql-roundtrip on getting archived state --- CHANGELOG.md | 8 ---- src/chat.rs | 95 +++++++++++++++++++++++++++++++------------ src/dc_receive_imf.rs | 4 +- 3 files changed, 69 insertions(+), 38 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7533d3b29..f3efb271c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,13 +1,5 @@ # Changelog -## Unreleased - -- pinned chats #1248 - -#### Api Changes - - - ChatInfo.archived is now a tri-state - ## 1.0.0-beta.24 - fix oauth2/gmail bug introduced in beta23 (not used in releases) #1219 diff --git a/src/chat.rs b/src/chat.rs index 9e7cb847f..441fd0772 100644 --- a/src/chat.rs +++ b/src/chat.rs @@ -177,20 +177,16 @@ impl ChatId { Ok(()) } - pub fn get_archive_state(self, context: &Context) -> ArchiveState { - if self.is_special() { - return ArchiveState::Normal; - } - if let Some(archive_state) = context.sql.query_get_value::<_, ArchiveState>( + // note that unarchive() is not the same as set_archived(false) - + // eg. unarchive() does not send events as done for set_archived(false). + pub fn unarchive(self, context: &Context) -> Result<(), Error> { + sql::execute( context, - "SELECT archived FROM chats WHERE id=?;", + &context.sql, + "UPDATE chats SET archived=0 WHERE id=? and archived=1", params![self], - ) { - archive_state - } else { - // if it failed return normal to be safe - ArchiveState::Normal - } + )?; + Ok(()) } /// Deletes a chat. @@ -434,6 +430,7 @@ pub struct Chat { pub id: ChatId, pub typ: Chattype, pub name: String, + pub visibility: ArchiveState, pub grpid: String, blocked: Blocked, pub param: Params, @@ -457,6 +454,7 @@ impl Chat { name: row.get::<_, String>(1)?, grpid: row.get::<_, String>(2)?, param: row.get::<_, String>(3)?.parse().unwrap_or_default(), + visibility: row.get(4)?, blocked: row.get::<_, Option<_>>(5)?.unwrap_or_default(), is_sending_locations: row.get(6)?, mute_duration: row.get(7)?, @@ -663,12 +661,11 @@ impl Chat { Some(message) => message.text.unwrap_or_else(String::new), _ => String::new(), }; - let archive_state = self.id.get_archive_state(context); Ok(ChatInfo { id: self.id, type_: self.typ as u32, name: self.name.clone(), - archived: archive_state, + archived: self.visibility == ArchiveState::Archived, param: self.param.to_string(), gossiped_timestamp: self.get_gossiped_timestamp(context), is_sending_locations: self.is_sending_locations, @@ -680,6 +677,10 @@ impl Chat { }) } + pub fn get_visibility(&self) -> ArchiveState { + self.visibility + } + pub fn is_unpromoted(&self) -> bool { self.param.get_int(Param::Unpromoted).unwrap_or_default() == 1 } @@ -935,7 +936,7 @@ impl Chat { } } -#[derive(Debug, Eq, PartialEq, Clone, Serialize, Deserialize)] +#[derive(Debug, Copy, Eq, PartialEq, Clone, Serialize, Deserialize)] pub enum ArchiveState { /// Neither archived or pinned Normal = 0, @@ -993,7 +994,7 @@ pub struct ChatInfo { pub name: String, /// Whether the chat is archived. - pub archived: ArchiveState, + pub archived: bool, /// The "params" of the chat. /// @@ -2531,7 +2532,7 @@ mod tests { "id": 10, "type": 100, "name": "bob", - "archived": 0, + "archived": false, "param": "", "gossiped_timestamp": 0, "is_sending_locations": false, @@ -2605,7 +2606,7 @@ mod tests { let chat = Chat::load_from_db(&t.ctx, chat_id).unwrap(); assert_eq!(chat.id, chat_id); assert!(chat.is_self_talk()); - assert!(chat_id.get_archive_state(&t.ctx) == ArchiveState::Normal); + assert!(chat.visibility == ArchiveState::Normal); assert!(!chat.is_device_talk()); assert!(chat.can_send()); assert_eq!(chat.name, t.ctx.stock_str(StockMessage::SavedMessages)); @@ -2619,7 +2620,7 @@ mod tests { assert_eq!(DC_CHAT_ID_DEADDROP, 1); assert!(chat.id.is_deaddrop()); assert!(!chat.is_self_talk()); - assert!(chat.get_id().get_archive_state(&t.ctx) == ArchiveState::Normal); + assert!(chat.visibility == ArchiveState::Normal); assert!(!chat.is_device_talk()); assert!(!chat.can_send()); assert_eq!(chat.name, t.ctx.stock_str(StockMessage::DeadDrop)); @@ -2828,8 +2829,18 @@ mod tests { assert!(chat_id1 .set_archive_state(&t.ctx, ArchiveState::Archived) .is_ok()); - assert!(chat_id1.get_archive_state(&t.ctx) == ArchiveState::Archived); - assert!(chat_id2.get_archive_state(&t.ctx) == ArchiveState::Normal); + assert!( + Chat::load_from_db(&t.ctx, chat_id1) + .unwrap() + .get_visibility() + == ArchiveState::Archived + ); + assert!( + Chat::load_from_db(&t.ctx, chat_id2) + .unwrap() + .get_visibility() + == ArchiveState::Normal + ); assert_eq!(get_chat_cnt(&t.ctx), 2); assert_eq!(chatlist_len(&t.ctx, 0), 2); // including DC_CHAT_ID_ARCHIVED_LINK now assert_eq!(chatlist_len(&t.ctx, DC_GCL_NO_SPECIALS), 1); @@ -2839,8 +2850,18 @@ mod tests { assert!(chat_id2 .set_archive_state(&t.ctx, ArchiveState::Archived) .is_ok()); - assert!(chat_id1.get_archive_state(&t.ctx) == ArchiveState::Archived); - assert!(chat_id2.get_archive_state(&t.ctx) == ArchiveState::Archived); + assert!( + Chat::load_from_db(&t.ctx, chat_id1) + .unwrap() + .get_visibility() + == ArchiveState::Archived + ); + assert!( + Chat::load_from_db(&t.ctx, chat_id2) + .unwrap() + .get_visibility() + == ArchiveState::Archived + ); assert_eq!(get_chat_cnt(&t.ctx), 2); assert_eq!(chatlist_len(&t.ctx, 0), 1); // only DC_CHAT_ID_ARCHIVED_LINK now assert_eq!(chatlist_len(&t.ctx, DC_GCL_NO_SPECIALS), 0); @@ -2856,8 +2877,18 @@ mod tests { assert!(chat_id2 .set_archive_state(&t.ctx, ArchiveState::Normal) .is_ok()); - assert!(chat_id1.get_archive_state(&t.ctx) == ArchiveState::Archived); - assert!(chat_id2.get_archive_state(&t.ctx) == ArchiveState::Normal); + assert!( + Chat::load_from_db(&t.ctx, chat_id1) + .unwrap() + .get_visibility() + == ArchiveState::Archived + ); + assert!( + Chat::load_from_db(&t.ctx, chat_id2) + .unwrap() + .get_visibility() + == ArchiveState::Normal + ); 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), 1); @@ -2895,7 +2926,12 @@ mod tests { .is_ok() == true ); - assert_eq!(chat_id1.get_archive_state(&t.ctx), ArchiveState::Pinned); + assert_eq!( + Chat::load_from_db(&t.ctx, chat_id1) + .unwrap() + .get_visibility(), + ArchiveState::Pinned + ); // check if chat order changed let chatlist = get_chats_from_chat_list(&t.ctx, DC_GCL_NO_SPECIALS); @@ -2908,7 +2944,12 @@ mod tests { .is_ok() == true ); - assert_eq!(chat_id1.get_archive_state(&t.ctx), ArchiveState::Normal); + assert_eq!( + Chat::load_from_db(&t.ctx, chat_id1) + .unwrap() + .get_visibility(), + ArchiveState::Normal + ); // check if chat order changed back let chatlist = get_chats_from_chat_list(&t.ctx, DC_GCL_NO_SPECIALS); diff --git a/src/dc_receive_imf.rs b/src/dc_receive_imf.rs index 36f6f8d9c..5f3b89646 100644 --- a/src/dc_receive_imf.rs +++ b/src/dc_receive_imf.rs @@ -548,9 +548,7 @@ fn add_parts( ); // unarchive chat - if chat_id.get_archive_state(context) == ArchiveState::Archived { - chat_id.set_archive_state(context, ArchiveState::Normal)?; - } + chat_id.unarchive(context)?; // if the mime-headers should be saved, find out its size // (the mime-header ends with an empty line) From 0303ea7f57defe056e7bb1b7cd8412348bd46c5f Mon Sep 17 00:00:00 2001 From: "B. Petersen" Date: Thu, 13 Feb 2020 19:14:44 +0100 Subject: [PATCH 11/17] rename to ChatVisibility, simplify ffi --- deltachat-ffi/deltachat.h | 1 - deltachat-ffi/src/lib.rs | 32 ++++++++--------- examples/repl/cmdline.rs | 20 +++++------ src/chat.rs | 74 +++++++++++++++++++-------------------- src/chatlist.rs | 2 +- src/dc_receive_imf.rs | 2 +- 6 files changed, 63 insertions(+), 68 deletions(-) diff --git a/deltachat-ffi/deltachat.h b/deltachat-ffi/deltachat.h index aa3bd47cb..311106525 100644 --- a/deltachat-ffi/deltachat.h +++ b/deltachat-ffi/deltachat.h @@ -2693,7 +2693,6 @@ dc_context_t* dc_chatlist_get_context (dc_chatlist_t* chatlist); * * id: chat id * name: chat/group name - * visibility: one of @ref DC_CHAT_VISIBILITY * color: color of this chat * last-message-from: who sent the last message * last-message-text: message (truncated) diff --git a/deltachat-ffi/src/lib.rs b/deltachat-ffi/src/lib.rs index cfba118c1..da6913433 100644 --- a/deltachat-ffi/src/lib.rs +++ b/deltachat-ffi/src/lib.rs @@ -25,7 +25,7 @@ use std::time::{Duration, SystemTime}; use libc::uintptr_t; use num_traits::{FromPrimitive, ToPrimitive}; -use deltachat::chat::{ArchiveState, ChatId, MuteDuration}; +use deltachat::chat::{ChatId, ChatVisibility, MuteDuration}; use deltachat::constants::DC_MSG_ID_LAST_SPECIAL; use deltachat::contact::Contact; use deltachat::context::Context; @@ -1198,21 +1198,22 @@ pub unsafe extern "C" fn dc_set_chat_visibility( return; } let ffi_context = &*context; - let archive_state = match archive { - 0 => ArchiveState::Normal, - 1 => ArchiveState::Archived, - 2 => ArchiveState::Pinned, + let visibility = match archive { + 0 => ChatVisibility::Normal, + 1 => ChatVisibility::Archived, + 2 => ChatVisibility::Pinned, _ => { - ffi_context - .warning("ignoring careless call to dc_set_chat_visibility(): unknown archived state"); + ffi_context.warning( + "ignoring careless call to dc_set_chat_visibility(): unknown archived state", + ); return; } }; ffi_context .with_inner(|ctx| { ChatId::new(chat_id) - .set_archive_state(ctx, archive_state) - .log_err(ffi_context, "Failed archive chat") + .set_visibility(ctx, visibility) + .log_err(ffi_context, "Failed setting chat visibility") .unwrap_or(()) }) .unwrap_or(()) @@ -2462,14 +2463,11 @@ pub unsafe extern "C" fn dc_chat_get_visibility(chat: *mut dc_chat_t) -> libc::c return 0; } let ffi_chat = &*chat; - let ffi_context = &*ffi_chat.context; - ffi_context - .with_inner(|ctx| match ffi_chat.chat.get_id().get_archive_state(ctx) { - ArchiveState::Normal => 0, - ArchiveState::Archived => 1, - ArchiveState::Pinned => 2, - } as libc::c_int) - .unwrap_or_else(|_| 0) + match ffi_chat.chat.visibility { + ChatVisibility::Normal => 0, + ChatVisibility::Archived => 1, + ChatVisibility::Pinned => 2, + } } #[no_mangle] diff --git a/examples/repl/cmdline.rs b/examples/repl/cmdline.rs index f231bf9fe..c7a260139 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, ArchiveState, Chat, ChatId}; +use deltachat::chat::{self, Chat, ChatId, ChatVisibility}; use deltachat::chatlist::*; use deltachat::constants::*; use deltachat::contact::*; @@ -518,15 +518,15 @@ pub fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::Error> { chat.get_id(), chat.get_name(), chat.get_id().get_fresh_msg_cnt(context), - match chat.get_id().get_archive_state(context) { - ArchiveState::Normal => "", - ArchiveState::Archived => "📦", - ArchiveState::Pinned => "📌", + match chat.visibility { + ChatVisibility::Normal => "", + ChatVisibility::Archived => "📦", + ChatVisibility::Pinned => "📌", }, ); let lot = chatlist.get_summary(context, i, Some(&chat)); let statestr = - if chat.get_id().get_archive_state(context) == ArchiveState::Archived { + if chat.get_id().get_visibility(context) == ChatVisibility::Archived { " [Archived]" } else { match lot.get_state() { @@ -853,12 +853,12 @@ pub fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::Error> { "archive" | "unarchive" | "pin" | "unpin" => { ensure!(!arg1.is_empty(), "Argument missing."); let chat_id = ChatId::new(arg1.parse()?); - chat_id.set_archive_state( + chat_id.set_visibility( context, match arg0 { - "archive" => ArchiveState::Archived, - "unarchive" | "unpin" => ArchiveState::Normal, - "pin" => ArchiveState::Pinned, + "archive" => ChatVisibility::Archived, + "unarchive" | "unpin" => ChatVisibility::Normal, + "pin" => ChatVisibility::Pinned, _ => panic!("Unexpected command (This should never happen)"), }, )?; diff --git a/src/chat.rs b/src/chat.rs index 441fd0772..784fd2e51 100644 --- a/src/chat.rs +++ b/src/chat.rs @@ -137,10 +137,10 @@ impl ChatId { } /// Archives or unarchives a chat. - pub fn set_archive_state( + pub fn set_visibility( self, context: &Context, - new_archive_state: ArchiveState, + visibility: ChatVisibility, ) -> Result<(), Error> { ensure!( !self.is_special(), @@ -148,7 +148,7 @@ impl ChatId { self ); - let send_event = if new_archive_state == ArchiveState::Archived { + let send_event = if visibility == ChatVisibility::Archived { sql::execute( context, &context.sql, @@ -164,7 +164,7 @@ impl ChatId { context, &context.sql, "UPDATE chats SET archived=? WHERE id=?;", - params![new_archive_state, self], + params![visibility, self], )?; if send_event { @@ -430,7 +430,7 @@ pub struct Chat { pub id: ChatId, pub typ: Chattype, pub name: String, - pub visibility: ArchiveState, + pub visibility: ChatVisibility, pub grpid: String, blocked: Blocked, pub param: Params, @@ -665,7 +665,7 @@ impl Chat { id: self.id, type_: self.typ as u32, name: self.name.clone(), - archived: self.visibility == ArchiveState::Archived, + archived: self.visibility == ChatVisibility::Archived, param: self.param.to_string(), gossiped_timestamp: self.get_gossiped_timestamp(context), is_sending_locations: self.is_sending_locations, @@ -677,7 +677,7 @@ impl Chat { }) } - pub fn get_visibility(&self) -> ArchiveState { + pub fn get_visibility(&self) -> ChatVisibility { self.visibility } @@ -937,20 +937,18 @@ impl Chat { } #[derive(Debug, Copy, Eq, PartialEq, Clone, Serialize, Deserialize)] -pub enum ArchiveState { - /// Neither archived or pinned +pub enum ChatVisibility { Normal = 0, Archived = 1, - /// Pinned (formaly known as sticky) Pinned = 2, } -impl rusqlite::types::ToSql for ArchiveState { +impl rusqlite::types::ToSql for ChatVisibility { fn to_sql(&self) -> rusqlite::Result { let duration = match &self { - ArchiveState::Normal => 0, - ArchiveState::Archived => 1, - ArchiveState::Pinned => 2, + ChatVisibility::Normal => 0, + ChatVisibility::Archived => 1, + ChatVisibility::Pinned => 2, }; let val = rusqlite::types::Value::Integer(duration as i64); let out = rusqlite::types::ToSqlOutput::Owned(val); @@ -958,13 +956,13 @@ impl rusqlite::types::ToSql for ArchiveState { } } -impl rusqlite::types::FromSql for ArchiveState { +impl rusqlite::types::FromSql for ChatVisibility { fn column_result(value: rusqlite::types::ValueRef) -> rusqlite::types::FromSqlResult { i64::column_result(value).and_then(|val| { match val { - 2 => Ok(ArchiveState::Pinned), - 1 => Ok(ArchiveState::Archived), - 0 => Ok(ArchiveState::Normal), + 2 => Ok(ChatVisibility::Pinned), + 1 => Ok(ChatVisibility::Archived), + 0 => Ok(ChatVisibility::Normal), n => { // unknown archived state, falling back to normal state (was this db opened with a newer deltachat version?) Err(rusqlite::types::FromSqlError::OutOfRange(n)) @@ -1347,7 +1345,7 @@ fn prepare_msg_common( ) -> Result { msg.id = MsgId::new_unset(); prepare_msg_blob(context, msg)?; - chat_id.set_archive_state(context, ArchiveState::Normal)?; + chat_id.set_visibility(context, ChatVisibility::Normal)?; let mut chat = Chat::load_from_db(context, chat_id)?; ensure!(chat.can_send(), "cannot send to {}", chat_id); @@ -2272,7 +2270,7 @@ pub fn forward_msgs(context: &Context, msg_ids: &[MsgId], chat_id: ChatId) -> Re let mut created_msgs: Vec = Vec::new(); let mut curr_timestamp: i64; - chat_id.set_archive_state(context, ArchiveState::Normal)?; + chat_id.set_visibility(context, ChatVisibility::Normal)?; if let Ok(mut chat) = Chat::load_from_db(context, chat_id) { ensure!(chat.can_send(), "cannot send to {}", chat_id); curr_timestamp = dc_create_smeared_timestamps(context, msg_ids.len()); @@ -2415,7 +2413,7 @@ pub fn add_device_msg( let rfc724_mid = dc_create_outgoing_rfc724_mid(None, "@device"); msg.try_calc_and_set_dimensions(context).ok(); prepare_msg_blob(context, msg)?; - chat_id.set_archive_state(context, ArchiveState::Normal)?; + chat_id.set_visibility(context, ChatVisibility::Normal)?; context.sql.execute( "INSERT INTO msgs (chat_id,from_id,to_id, timestamp,type,state, txt,param,rfc724_mid) \ @@ -2606,7 +2604,7 @@ mod tests { let chat = Chat::load_from_db(&t.ctx, chat_id).unwrap(); assert_eq!(chat.id, chat_id); assert!(chat.is_self_talk()); - assert!(chat.visibility == ArchiveState::Normal); + assert!(chat.visibility == ChatVisibility::Normal); assert!(!chat.is_device_talk()); assert!(chat.can_send()); assert_eq!(chat.name, t.ctx.stock_str(StockMessage::SavedMessages)); @@ -2620,7 +2618,7 @@ mod tests { assert_eq!(DC_CHAT_ID_DEADDROP, 1); assert!(chat.id.is_deaddrop()); assert!(!chat.is_self_talk()); - assert!(chat.visibility == ArchiveState::Normal); + assert!(chat.visibility == ChatVisibility::Normal); assert!(!chat.is_device_talk()); assert!(!chat.can_send()); assert_eq!(chat.name, t.ctx.stock_str(StockMessage::DeadDrop)); @@ -2827,19 +2825,19 @@ mod tests { // archive first chat assert!(chat_id1 - .set_archive_state(&t.ctx, ArchiveState::Archived) + .set_visibility(&t.ctx, ChatVisibility::Archived) .is_ok()); assert!( Chat::load_from_db(&t.ctx, chat_id1) .unwrap() .get_visibility() - == ArchiveState::Archived + == ChatVisibility::Archived ); assert!( Chat::load_from_db(&t.ctx, chat_id2) .unwrap() .get_visibility() - == ArchiveState::Normal + == ChatVisibility::Normal ); assert_eq!(get_chat_cnt(&t.ctx), 2); assert_eq!(chatlist_len(&t.ctx, 0), 2); // including DC_CHAT_ID_ARCHIVED_LINK now @@ -2848,19 +2846,19 @@ mod tests { // archive second chat assert!(chat_id2 - .set_archive_state(&t.ctx, ArchiveState::Archived) + .set_visibility(&t.ctx, ChatVisibility::Archived) .is_ok()); assert!( Chat::load_from_db(&t.ctx, chat_id1) .unwrap() .get_visibility() - == ArchiveState::Archived + == ChatVisibility::Archived ); assert!( Chat::load_from_db(&t.ctx, chat_id2) .unwrap() .get_visibility() - == ArchiveState::Archived + == ChatVisibility::Archived ); assert_eq!(get_chat_cnt(&t.ctx), 2); assert_eq!(chatlist_len(&t.ctx, 0), 1); // only DC_CHAT_ID_ARCHIVED_LINK now @@ -2869,25 +2867,25 @@ mod tests { // archive already archived first chat, unarchive second chat two times assert!(chat_id1 - .set_archive_state(&t.ctx, ArchiveState::Archived) + .set_visibility(&t.ctx, ChatVisibility::Archived) .is_ok()); assert!(chat_id2 - .set_archive_state(&t.ctx, ArchiveState::Normal) + .set_visibility(&t.ctx, ChatVisibility::Normal) .is_ok()); assert!(chat_id2 - .set_archive_state(&t.ctx, ArchiveState::Normal) + .set_visibility(&t.ctx, ChatVisibility::Normal) .is_ok()); assert!( Chat::load_from_db(&t.ctx, chat_id1) .unwrap() .get_visibility() - == ArchiveState::Archived + == ChatVisibility::Archived ); assert!( Chat::load_from_db(&t.ctx, chat_id2) .unwrap() .get_visibility() - == ArchiveState::Normal + == ChatVisibility::Normal ); assert_eq!(get_chat_cnt(&t.ctx), 2); assert_eq!(chatlist_len(&t.ctx, 0), 2); @@ -2922,7 +2920,7 @@ mod tests { // pin assert!( chat_id1 - .set_archive_state(&t.ctx, ArchiveState::Pinned) + .set_visibility(&t.ctx, ChatVisibility::Pinned) .is_ok() == true ); @@ -2930,7 +2928,7 @@ mod tests { Chat::load_from_db(&t.ctx, chat_id1) .unwrap() .get_visibility(), - ArchiveState::Pinned + ChatVisibility::Pinned ); // check if chat order changed @@ -2940,7 +2938,7 @@ mod tests { // unpin assert!( chat_id1 - .set_archive_state(&t.ctx, ArchiveState::Normal) + .set_visibility(&t.ctx, ChatVisibility::Normal) .is_ok() == true ); @@ -2948,7 +2946,7 @@ mod tests { Chat::load_from_db(&t.ctx, chat_id1) .unwrap() .get_visibility(), - ArchiveState::Normal + ChatVisibility::Normal ); // check if chat order changed back diff --git a/src/chatlist.rs b/src/chatlist.rs index 297883ab7..b0f978bb0 100644 --- a/src/chatlist.rs +++ b/src/chatlist.rs @@ -393,7 +393,7 @@ mod tests { assert_eq!(chats.len(), 0); chat_id1 - .set_archive_state(&t.ctx, ArchiveState::Archived) + .set_visibility(&t.ctx, ChatVisibility::Archived) .ok(); let chats = Chatlist::try_load(&t.ctx, DC_GCL_ARCHIVED_ONLY, None, None).unwrap(); assert_eq!(chats.len(), 1); diff --git a/src/dc_receive_imf.rs b/src/dc_receive_imf.rs index 5f3b89646..e0bb62fe8 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, ArchiveState, Chat, ChatId}; +use crate::chat::{self, Chat, ChatId}; use crate::config::Config; use crate::constants::*; use crate::contact::*; From 4cfa9e6165322ec0b1cb75b085b82544742b0c04 Mon Sep 17 00:00:00 2001 From: "B. Petersen" Date: Thu, 13 Feb 2020 19:31:57 +0100 Subject: [PATCH 12/17] send event as before, uis depend on that --- src/chat.rs | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/src/chat.rs b/src/chat.rs index 784fd2e51..8eec9c1ab 100644 --- a/src/chat.rs +++ b/src/chat.rs @@ -148,17 +148,14 @@ impl ChatId { self ); - let send_event = if visibility == ChatVisibility::Archived { + if visibility == ChatVisibility::Archived { sql::execute( context, &context.sql, "UPDATE msgs SET state=? WHERE chat_id=? AND state=?;", params![MessageState::InNoticed, self, MessageState::InFresh], )?; - true - } else { - false - }; + } sql::execute( context, @@ -167,12 +164,10 @@ impl ChatId { params![visibility, self], )?; - if send_event { - context.call_cb(Event::MsgsChanged { - msg_id: MsgId::new(0), - chat_id: ChatId::new(0), - }); - } + context.call_cb(Event::MsgsChanged { + msg_id: MsgId::new(0), + chat_id: ChatId::new(0), + }); Ok(()) } From f25d5dd1232da7855cb2f7dce964a8043a1866ed Mon Sep 17 00:00:00 2001 From: "B. Petersen" Date: Thu, 13 Feb 2020 19:42:06 +0100 Subject: [PATCH 13/17] do not unpin chats on sending/receiving messages --- src/chat.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/chat.rs b/src/chat.rs index 8eec9c1ab..a1583fb3f 100644 --- a/src/chat.rs +++ b/src/chat.rs @@ -172,8 +172,8 @@ impl ChatId { Ok(()) } - // note that unarchive() is not the same as set_archived(false) - - // eg. unarchive() does not send events as done for set_archived(false). + // note that unarchive() is not the same as set_visibility(Normal) - + // eg. unarchive() does not modify pinned chats and does not send events. pub fn unarchive(self, context: &Context) -> Result<(), Error> { sql::execute( context, @@ -1340,7 +1340,7 @@ fn prepare_msg_common( ) -> Result { msg.id = MsgId::new_unset(); prepare_msg_blob(context, msg)?; - chat_id.set_visibility(context, ChatVisibility::Normal)?; + chat_id.unarchive(context)?; let mut chat = Chat::load_from_db(context, chat_id)?; ensure!(chat.can_send(), "cannot send to {}", chat_id); @@ -2265,7 +2265,7 @@ pub fn forward_msgs(context: &Context, msg_ids: &[MsgId], chat_id: ChatId) -> Re let mut created_msgs: Vec = Vec::new(); let mut curr_timestamp: i64; - chat_id.set_visibility(context, ChatVisibility::Normal)?; + chat_id.unarchive(context)?; if let Ok(mut chat) = Chat::load_from_db(context, chat_id) { ensure!(chat.can_send(), "cannot send to {}", chat_id); curr_timestamp = dc_create_smeared_timestamps(context, msg_ids.len()); @@ -2408,7 +2408,7 @@ pub fn add_device_msg( let rfc724_mid = dc_create_outgoing_rfc724_mid(None, "@device"); msg.try_calc_and_set_dimensions(context).ok(); prepare_msg_blob(context, msg)?; - chat_id.set_visibility(context, ChatVisibility::Normal)?; + chat_id.unarchive(context)?; context.sql.execute( "INSERT INTO msgs (chat_id,from_id,to_id, timestamp,type,state, txt,param,rfc724_mid) \ From a177df32b7dd17b4c99d4c070be6a7a6c9319a51 Mon Sep 17 00:00:00 2001 From: "B. Petersen" Date: Fri, 14 Feb 2020 00:25:44 +0100 Subject: [PATCH 14/17] omit values in ChatVisibility enum as suggested by @dignifiedquire and @flub --- src/chat.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/chat.rs b/src/chat.rs index a1583fb3f..37a4dabae 100644 --- a/src/chat.rs +++ b/src/chat.rs @@ -933,19 +933,19 @@ impl Chat { #[derive(Debug, Copy, Eq, PartialEq, Clone, Serialize, Deserialize)] pub enum ChatVisibility { - Normal = 0, - Archived = 1, - Pinned = 2, + Normal, + Archived, + Pinned, } impl rusqlite::types::ToSql for ChatVisibility { fn to_sql(&self) -> rusqlite::Result { - let duration = match &self { + let visibility = match &self { ChatVisibility::Normal => 0, ChatVisibility::Archived => 1, ChatVisibility::Pinned => 2, }; - let val = rusqlite::types::Value::Integer(duration as i64); + let val = rusqlite::types::Value::Integer(visibility); let out = rusqlite::types::ToSqlOutput::Owned(val); Ok(out) } From 84f8627890a9f0528775af297544374702d0045d Mon Sep 17 00:00:00 2001 From: "B. Petersen" Date: Fri, 14 Feb 2020 11:43:34 +0100 Subject: [PATCH 15/17] fix repl tool --- examples/repl/cmdline.rs | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/examples/repl/cmdline.rs b/examples/repl/cmdline.rs index c7a260139..b065f5845 100644 --- a/examples/repl/cmdline.rs +++ b/examples/repl/cmdline.rs @@ -525,18 +525,17 @@ pub fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::Error> { }, ); let lot = chatlist.get_summary(context, i, Some(&chat)); - let statestr = - if chat.get_id().get_visibility(context) == ChatVisibility::Archived { - " [Archived]" - } else { - match lot.get_state() { - LotState::MsgOutPending => " o", - LotState::MsgOutDelivered => " √", - LotState::MsgOutMdnRcvd => " √√", - LotState::MsgOutFailed => " !!", - _ => "", - } - }; + let statestr = if chat.visibility == ChatVisibility::Archived { + " [Archived]" + } else { + match lot.get_state() { + LotState::MsgOutPending => " o", + LotState::MsgOutDelivered => " √", + LotState::MsgOutMdnRcvd => " √√", + LotState::MsgOutFailed => " !!", + _ => "", + } + }; let timestr = dc_timestamp_to_str(lot.get_timestamp()); let text1 = lot.get_text1(); let text2 = lot.get_text2(); @@ -985,7 +984,10 @@ pub fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::Error> { } "setqr" => { ensure!(!arg1.is_empty(), "Argument missing."); - set_config_from_qr(context, arg1); + match set_config_from_qr(context, arg1) { + Ok(()) => println!("Config set from QR code, you can now call 'configure'"), + Err(err) => println!("Cannot set config from QR code: {:?}", err), + } } "providerinfo" => { ensure!(!arg1.is_empty(), "Argument missing."); From 1f9520dc787350f67525c22777a5532f92c4b8fa Mon Sep 17 00:00:00 2001 From: "B. Petersen" Date: Fri, 14 Feb 2020 11:56:59 +0100 Subject: [PATCH 16/17] target comments from @flub --- src/chat.rs | 6 ++---- src/chatlist.rs | 8 ++++---- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/src/chat.rs b/src/chat.rs index 37a4dabae..745739a34 100644 --- a/src/chat.rs +++ b/src/chat.rs @@ -958,10 +958,8 @@ impl rusqlite::types::FromSql for ChatVisibility { 2 => Ok(ChatVisibility::Pinned), 1 => Ok(ChatVisibility::Archived), 0 => Ok(ChatVisibility::Normal), - n => { - // unknown archived state, falling back to normal state (was this db opened with a newer deltachat version?) - Err(rusqlite::types::FromSqlError::OutOfRange(n)) - } + // fallback to to Normal for unknown values, may happen eg. on imports created by a newer version. + _ => Ok(ChatVisibility::Normal), } }) } diff --git a/src/chatlist.rs b/src/chatlist.rs index b0f978bb0..0d2967dbf 100644 --- a/src/chatlist.rs +++ b/src/chatlist.rs @@ -199,13 +199,13 @@ impl Chatlist { SELECT MAX(timestamp) FROM msgs WHERE chat_id=c.id - AND (hidden=0 OR state=?)) + AND (hidden=0 OR state=?1)) WHERE c.id>9 AND c.blocked=0 - AND NOT c.archived=1 + AND NOT c.archived=?2 GROUP BY c.id - ORDER BY c.archived=2 DESC, IFNULL(m.timestamp,c.created_timestamp) DESC, m.id DESC;", - params![MessageState::OutDraft], + ORDER BY c.archived=?3 DESC, IFNULL(m.timestamp,c.created_timestamp) DESC, m.id DESC;", + params![MessageState::OutDraft, ChatVisibility::Archived, ChatVisibility::Pinned], process_row, process_rows, )?; From c8a8dbbbae55d4026f87020ba54ca178806462f8 Mon Sep 17 00:00:00 2001 From: "B. Petersen" Date: Fri, 14 Feb 2020 12:45:03 +0100 Subject: [PATCH 17/17] adapt python bindings --- python/src/deltachat/chat.py | 28 +--------------------------- python/src/deltachat/const.py | 6 +++--- python/tests/test_account.py | 11 ----------- 3 files changed, 4 insertions(+), 41 deletions(-) diff --git a/python/src/deltachat/chat.py b/python/src/deltachat/chat.py index 98b3220b5..09883f452 100644 --- a/python/src/deltachat/chat.py +++ b/python/src/deltachat/chat.py @@ -423,33 +423,7 @@ class Chat(object): """return True if this chat is archived. :returns: True if archived. """ - return lib.dc_chat_get_archived(self._dc_chat) == const.DC_CHAT_ARCHIVE_STATE_ARCHIVED - - def is_pinned(self): - """return True if this chat is pinned. - :returns: True if pinned. - """ - return lib.dc_chat_get_archived(self._dc_chat) == const.DC_CHAT_ARCHIVE_STATE_PINNED - - def archive(self): - """archive the chat. - """ - lib.dc_archive_chat(self._dc_context, self.id, const.DC_CHAT_ARCHIVE_STATE_ARCHIVED) - - def unarchive(self): - """unarchive the chat. - """ - lib.dc_archive_chat(self._dc_context, self.id, const.DC_CHAT_ARCHIVE_STATE_NORMAL) - - def pin(self): - """pin the chat. - """ - lib.dc_archive_chat(self._dc_context, self.id, const.DC_CHAT_ARCHIVE_STATE_PINNED) - - def unpin(self): - """unpin the chat. (same as unarchive) - """ - self.unarchive() + return lib.dc_chat_get_visibility(self._dc_chat) == const.DC_CHAT_VISIBILITY_ARCHIVED def enable_sending_locations(self, seconds): """enable sending locations for this chat. diff --git a/python/src/deltachat/const.py b/python/src/deltachat/const.py index 0c898d6b7..189943268 100644 --- a/python/src/deltachat/const.py +++ b/python/src/deltachat/const.py @@ -33,9 +33,9 @@ DC_CHAT_TYPE_UNDEFINED = 0 DC_CHAT_TYPE_SINGLE = 100 DC_CHAT_TYPE_GROUP = 120 DC_CHAT_TYPE_VERIFIED_GROUP = 130 -DC_CHAT_ARCHIVE_STATE_NORMAL = 0 -DC_CHAT_ARCHIVE_STATE_ARCHIVED = 1 -DC_CHAT_ARCHIVE_STATE_PINNED = 2 +DC_CHAT_VISIBILITY_NORMAL = 0 +DC_CHAT_VISIBILITY_ARCHIVED = 1 +DC_CHAT_VISIBILITY_PINNED = 2 DC_MSG_ID_MARKER1 = 1 DC_MSG_ID_DAYMARKER = 9 DC_MSG_ID_LAST_SPECIAL = 9 diff --git a/python/tests/test_account.py b/python/tests/test_account.py index c06bde091..f31766e98 100644 --- a/python/tests/test_account.py +++ b/python/tests/test_account.py @@ -426,17 +426,6 @@ class TestOfflineChat: chat.remove_contact(contacts[3]) assert len(chat.get_contacts()) == 9 - def test_archive_and_pin_chat(self, ac1): - chat = ac1.create_group_chat(name="title1") - - assert not chat.is_archived() - chat.archive() - assert chat.is_archived() - chat.unarchive() - assert not chat.is_archived() - chat.pin() - assert chat.is_pinned() - class TestOnlineAccount: @pytest.mark.ignored