represent archivestate as enum

before it was a boolean, even though it is a 3 state
This commit is contained in:
Simon Laux
2020-02-08 20:47:20 +01:00
committed by B. Petersen
parent 7274197da0
commit 5678562ce2
7 changed files with 161 additions and 67 deletions

View File

@@ -25,6 +25,7 @@ use std::time::{Duration, SystemTime};
use libc::uintptr_t; use libc::uintptr_t;
use num_traits::{FromPrimitive, ToPrimitive}; use num_traits::{FromPrimitive, ToPrimitive};
use deltachat::chat::ArchiveState;
use deltachat::chat::ChatId; use deltachat::chat::ChatId;
use deltachat::chat::MuteDuration; use deltachat::chat::MuteDuration;
use deltachat::constants::DC_MSG_ID_LAST_SPECIAL; use deltachat::constants::DC_MSG_ID_LAST_SPECIAL;
@@ -1199,17 +1200,15 @@ pub unsafe extern "C" fn dc_archive_chat(
return; return;
} }
let ffi_context = &*context; let ffi_context = &*context;
let archive = if archive == 0 { let archive_state = match archive {
false 2 => ArchiveState::Pinned,
} else if archive == 1 { 1 => ArchiveState::Archived,
true _ => ArchiveState::Normal,
} else {
return;
}; };
ffi_context ffi_context
.with_inner(|ctx| { .with_inner(|ctx| {
ChatId::new(chat_id) ChatId::new(chat_id)
.set_archived(ctx, archive) .set_archive_state(ctx, archive_state)
.log_err(ffi_context, "Failed archive chat") .log_err(ffi_context, "Failed archive chat")
.unwrap_or(()) .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; return 0;
} }
let ffi_chat = &*chat; 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] #[no_mangle]

View File

@@ -1,7 +1,7 @@
use std::path::Path; use std::path::Path;
use std::str::FromStr; use std::str::FromStr;
use deltachat::chat::{self, Chat, ChatId}; use deltachat::chat::{self, ArchiveState, Chat, ChatId};
use deltachat::chatlist::*; use deltachat::chatlist::*;
use deltachat::constants::*; use deltachat::constants::*;
use deltachat::contact::*; use deltachat::contact::*;
@@ -371,6 +371,8 @@ pub fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::Error> {
listmedia\n\ listmedia\n\
archive <chat-id>\n\ archive <chat-id>\n\
unarchive <chat-id>\n\ unarchive <chat-id>\n\
pin <chat-id>\n\
unpin <chat-id>\n\
delchat <chat-id>\n\ delchat <chat-id>\n\
===========================Message commands==\n\ ===========================Message commands==\n\
listmsgs <query>\n\ listmsgs <query>\n\
@@ -511,14 +513,20 @@ pub fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::Error> {
for i in (0..cnt).rev() { for i in (0..cnt).rev() {
let chat = Chat::load_from_db(context, chatlist.get_chat_id(i))?; let chat = Chat::load_from_db(context, chatlist.get_chat_id(i))?;
println!( println!(
"{}#{}: {} [{} fresh]", "{}#{}: {} [{} fresh] {}",
chat_prefix(&chat), chat_prefix(&chat),
chat.get_id(), chat.get_id(),
chat.get_name(), chat.get_name(),
chat.get_id().get_fresh_msg_cnt(context), 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 lot = chatlist.get_summary(context, i, Some(&chat));
let statestr = if chat.is_archived() { let statestr =
if chat.get_id().get_archive_state(context) == ArchiveState::Archived {
" [Archived]" " [Archived]"
} else { } else {
match lot.get_state() { match lot.get_state() {
@@ -842,10 +850,20 @@ pub fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::Error> {
} }
print!("\n"); print!("\n");
} }
"archive" | "unarchive" => { "archive" | "unarchive" | "pin" | "unpin" => {
ensure!(!arg1.is_empty(), "Argument <chat-id> missing."); ensure!(!arg1.is_empty(), "Argument <chat-id> missing.");
let chat_id = ChatId::new(arg1.parse()?); 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" => { "delchat" => {
ensure!(!arg1.is_empty(), "Argument <chat-id> missing."); ensure!(!arg1.is_empty(), "Argument <chat-id> missing.");

View File

@@ -263,7 +263,7 @@ const DB_COMMANDS: [&str; 11] = [
"housekeeping", "housekeeping",
]; ];
const CHAT_COMMANDS: [&str; 24] = [ const CHAT_COMMANDS: [&str; 26] = [
"listchats", "listchats",
"listarchived", "listarchived",
"chat", "chat",
@@ -287,6 +287,8 @@ const CHAT_COMMANDS: [&str; 24] = [
"listmedia", "listmedia",
"archive", "archive",
"unarchive", "unarchive",
"pin",
"unpin",
"delchat", "delchat",
]; ];
const MESSAGE_COMMANDS: [&str; 8] = [ const MESSAGE_COMMANDS: [&str; 8] = [

View File

@@ -423,7 +423,13 @@ class Chat(object):
"""return True if this chat is archived. """return True if this chat is archived.
:returns: True if 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): def enable_sending_locations(self, seconds):
"""enable sending locations for this chat. """enable sending locations for this chat.

View File

@@ -137,47 +137,60 @@ impl ChatId {
} }
/// Archives or unarchives a chat. /// 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!( ensure!(
!self.is_special(), !self.is_special(),
"bad chat_id, can not be special chat: {}", "bad chat_id, can not be special chat: {}",
self self
); );
if new_archived { let mut send_event = false;
if new_archive_state == ArchiveState::Archived {
sql::execute( sql::execute(
context, context,
&context.sql, &context.sql,
"UPDATE msgs SET state=? WHERE chat_id=? AND state=?;", "UPDATE msgs SET state=? WHERE chat_id=? AND state=?;",
params![MessageState::InNoticed, self, MessageState::InFresh], params![MessageState::InNoticed, self, MessageState::InFresh],
)?; )?;
send_event = true;
} }
sql::execute( sql::execute(
context, context,
&context.sql, &context.sql,
"UPDATE chats SET archived=? WHERE id=?;", "UPDATE chats SET archived=? WHERE id=?;",
params![new_archived, self], params![new_archive_state, self],
)?; )?;
if send_event {
context.call_cb(Event::MsgsChanged { context.call_cb(Event::MsgsChanged {
msg_id: MsgId::new(0), msg_id: MsgId::new(0),
chat_id: ChatId::new(0), chat_id: ChatId::new(0),
}); });
}
Ok(()) Ok(())
} }
// note that unarchive() is not the same as set_archived(false) - pub fn get_archive_state(self, context: &Context) -> ArchiveState {
// eg. unarchive() does not send events as done for set_archived(false). if self.is_special() {
pub fn unarchive(self, context: &Context) -> Result<(), Error> { return ArchiveState::Normal;
sql::execute( }
if let Some(archive_state) = context.sql.query_get_value::<_, ArchiveState>(
context, context,
&context.sql, "SELECT archived FROM chats WHERE id=?;",
"UPDATE chats SET archived=0 WHERE id=?",
params![self], params![self],
)?; ) {
Ok(()) archive_state
} else {
// if it failed return normal to be safe
ArchiveState::Normal
}
} }
/// Deletes a chat. /// Deletes a chat.
@@ -421,7 +434,6 @@ pub struct Chat {
pub id: ChatId, pub id: ChatId,
pub typ: Chattype, pub typ: Chattype,
pub name: String, pub name: String,
archived: bool,
pub grpid: String, pub grpid: String,
blocked: Blocked, blocked: Blocked,
pub param: Params, pub param: Params,
@@ -445,7 +457,6 @@ impl Chat {
name: row.get::<_, String>(1)?, name: row.get::<_, String>(1)?,
grpid: row.get::<_, String>(2)?, grpid: row.get::<_, String>(2)?,
param: row.get::<_, String>(3)?.parse().unwrap_or_default(), param: row.get::<_, String>(3)?.parse().unwrap_or_default(),
archived: row.get(4)?,
blocked: row.get::<_, Option<_>>(5)?.unwrap_or_default(), blocked: row.get::<_, Option<_>>(5)?.unwrap_or_default(),
is_sending_locations: row.get(6)?, is_sending_locations: row.get(6)?,
mute_duration: row.get(7)?, mute_duration: row.get(7)?,
@@ -652,11 +663,13 @@ impl Chat {
Some(message) => message.text.unwrap_or_else(String::new), Some(message) => message.text.unwrap_or_else(String::new),
_ => String::new(), _ => String::new(),
}; };
let archive_state = self.id.get_archive_state(context);
Ok(ChatInfo { Ok(ChatInfo {
id: self.id, id: self.id,
type_: self.typ as u32, type_: self.typ as u32,
name: self.name.clone(), name: self.name.clone(),
archived: self.archived, archived: archive_state == ArchiveState::Archived,
pinned: archive_state == ArchiveState::Pinned,
param: self.param.to_string(), param: self.param.to_string(),
gossiped_timestamp: self.get_gossiped_timestamp(context), gossiped_timestamp: self.get_gossiped_timestamp(context),
is_sending_locations: self.is_sending_locations, 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 { pub fn is_unpromoted(&self) -> bool {
self.param.get_int(Param::Unpromoted).unwrap_or_default() == 1 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<rusqlite::types::ToSqlOutput> {
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<Self> {
i64::column_result(value).and_then(|val| {
Ok({
match val {
2 => ArchiveState::Pinned,
1 => ArchiveState::Archived,
_ => ArchiveState::Normal,
}
})
})
}
}
/// The current state of a chat. /// The current state of a chat.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[non_exhaustive] #[non_exhaustive]
@@ -950,6 +994,9 @@ pub struct ChatInfo {
/// Whether the chat is archived. /// Whether the chat is archived.
pub archived: bool, pub archived: bool,
/// Wether the chat is pinned.
pub pinned: bool,
/// The "params" of the chat. /// The "params" of the chat.
/// ///
/// This is the string-serialised version of [Params] currently. /// This is the string-serialised version of [Params] currently.
@@ -1301,7 +1348,7 @@ fn prepare_msg_common(
) -> Result<MsgId, Error> { ) -> Result<MsgId, Error> {
msg.id = MsgId::new_unset(); msg.id = MsgId::new_unset();
prepare_msg_blob(context, msg)?; 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)?; let mut chat = Chat::load_from_db(context, chat_id)?;
ensure!(chat.can_send(), "cannot send to {}", 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<MsgId> = Vec::new(); let mut created_msgs: Vec<MsgId> = Vec::new();
let mut curr_timestamp: i64; 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) { if let Ok(mut chat) = Chat::load_from_db(context, chat_id) {
ensure!(chat.can_send(), "cannot send to {}", chat_id); ensure!(chat.can_send(), "cannot send to {}", chat_id);
curr_timestamp = dc_create_smeared_timestamps(context, msg_ids.len()); 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"); let rfc724_mid = dc_create_outgoing_rfc724_mid(None, "@device");
msg.try_calc_and_set_dimensions(context).ok(); msg.try_calc_and_set_dimensions(context).ok();
prepare_msg_blob(context, msg)?; prepare_msg_blob(context, msg)?;
chat_id.unarchive(context)?; chat_id.set_archive_state(context, ArchiveState::Normal)?;
context.sql.execute( context.sql.execute(
"INSERT INTO msgs (chat_id,from_id,to_id, timestamp,type,state, txt,param,rfc724_mid) \ "INSERT INTO msgs (chat_id,from_id,to_id, timestamp,type,state, txt,param,rfc724_mid) \
@@ -2487,6 +2534,7 @@ mod tests {
"type": 100, "type": 100,
"name": "bob", "name": "bob",
"archived": false, "archived": false,
"pinned": false,
"param": "", "param": "",
"gossiped_timestamp": 0, "gossiped_timestamp": 0,
"is_sending_locations": false, "is_sending_locations": false,
@@ -2560,7 +2608,7 @@ mod tests {
let chat = Chat::load_from_db(&t.ctx, chat_id).unwrap(); let chat = Chat::load_from_db(&t.ctx, chat_id).unwrap();
assert_eq!(chat.id, chat_id); assert_eq!(chat.id, chat_id);
assert!(chat.is_self_talk()); 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.is_device_talk());
assert!(chat.can_send()); assert!(chat.can_send());
assert_eq!(chat.name, t.ctx.stock_str(StockMessage::SavedMessages)); assert_eq!(chat.name, t.ctx.stock_str(StockMessage::SavedMessages));
@@ -2574,7 +2622,7 @@ mod tests {
assert_eq!(DC_CHAT_ID_DEADDROP, 1); assert_eq!(DC_CHAT_ID_DEADDROP, 1);
assert!(chat.id.is_deaddrop()); assert!(chat.id.is_deaddrop());
assert!(!chat.is_self_talk()); 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.is_device_talk());
assert!(!chat.can_send()); assert!(!chat.can_send());
assert_eq!(chat.name, t.ctx.stock_str(StockMessage::DeadDrop)); assert_eq!(chat.name, t.ctx.stock_str(StockMessage::DeadDrop));
@@ -2780,29 +2828,39 @@ mod tests {
assert_eq!(DC_GCL_NO_SPECIALS, 0x02); assert_eq!(DC_GCL_NO_SPECIALS, 0x02);
// archive first chat // archive first chat
assert!(chat_id1.set_archived(&t.ctx, true).is_ok()); assert!(chat_id1
assert!(Chat::load_from_db(&t.ctx, chat_id1).unwrap().is_archived()); .set_archive_state(&t.ctx, ArchiveState::Archived)
assert!(!Chat::load_from_db(&t.ctx, chat_id2).unwrap().is_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!(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, 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_NO_SPECIALS), 1);
assert_eq!(chatlist_len(&t.ctx, DC_GCL_ARCHIVED_ONLY), 1); assert_eq!(chatlist_len(&t.ctx, DC_GCL_ARCHIVED_ONLY), 1);
// archive second chat // archive second chat
assert!(chat_id2.set_archived(&t.ctx, true).is_ok()); assert!(chat_id2
assert!(Chat::load_from_db(&t.ctx, chat_id1).unwrap().is_archived()); .set_archive_state(&t.ctx, ArchiveState::Archived)
assert!(Chat::load_from_db(&t.ctx, chat_id2).unwrap().is_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!(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, 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_NO_SPECIALS), 0);
assert_eq!(chatlist_len(&t.ctx, DC_GCL_ARCHIVED_ONLY), 2); assert_eq!(chatlist_len(&t.ctx, DC_GCL_ARCHIVED_ONLY), 2);
// archive already archived first chat, unarchive second chat two times // archive already archived first chat, unarchive second chat two times
assert!(chat_id1.set_archived(&t.ctx, true).is_ok()); assert!(chat_id1
assert!(chat_id2.set_archived(&t.ctx, false).is_ok()); .set_archive_state(&t.ctx, ArchiveState::Archived)
assert!(chat_id2.set_archived(&t.ctx, false).is_ok()); .is_ok());
assert!(Chat::load_from_db(&t.ctx, chat_id1).unwrap().is_archived()); assert!(chat_id2
assert!(!Chat::load_from_db(&t.ctx, chat_id2).unwrap().is_archived()); .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!(get_chat_cnt(&t.ctx), 2);
assert_eq!(chatlist_len(&t.ctx, 0), 2); assert_eq!(chatlist_len(&t.ctx, 0), 2);
assert_eq!(chatlist_len(&t.ctx, DC_GCL_NO_SPECIALS), 1); assert_eq!(chatlist_len(&t.ctx, DC_GCL_NO_SPECIALS), 1);

View File

@@ -392,7 +392,9 @@ mod tests {
let chats = Chatlist::try_load(&t.ctx, DC_GCL_ARCHIVED_ONLY, None, None).unwrap(); let chats = Chatlist::try_load(&t.ctx, DC_GCL_ARCHIVED_ONLY, None, None).unwrap();
assert_eq!(chats.len(), 0); 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(); let chats = Chatlist::try_load(&t.ctx, DC_GCL_ARCHIVED_ONLY, None, None).unwrap();
assert_eq!(chats.len(), 1); assert_eq!(chats.len(), 1);
} }

View File

@@ -3,7 +3,7 @@ use sha2::{Digest, Sha256};
use num_traits::FromPrimitive; use num_traits::FromPrimitive;
use crate::chat::{self, Chat, ChatId}; use crate::chat::{self, ArchiveState, Chat, ChatId};
use crate::config::Config; use crate::config::Config;
use crate::constants::*; use crate::constants::*;
use crate::contact::*; use crate::contact::*;
@@ -548,7 +548,9 @@ fn add_parts(
); );
// unarchive chat // 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 // if the mime-headers should be saved, find out its size
// (the mime-header ends with an empty line) // (the mime-header ends with an empty line)