refactor(chatlist): rustify

This commit is contained in:
Friedel Ziegelmayer
2019-07-28 11:32:48 +02:00
committed by GitHub
parent 4902310138
commit b23ca26908
11 changed files with 465 additions and 433 deletions

View File

@@ -281,11 +281,20 @@ pub unsafe extern "C" fn dc_get_chatlist<'a>(
flags: libc::c_int,
query_str: *mut libc::c_char,
query_id: u32,
) -> *mut dc_chatlist::dc_chatlist_t<'a> {
) -> *mut dc_chatlist_t<'a> {
assert!(!context.is_null());
let context = &*context;
dc_chatlist::dc_get_chatlist(context, flags, query_str, query_id)
let qs = if query_str.is_null() {
None
} else {
Some(dc_tools::as_str(query_str))
};
let qi = if query_id == 0 { None } else { Some(query_id) };
match chatlist::Chatlist::try_load(context, flags as usize, qs, qi) {
Ok(list) => Box::into_raw(Box::new(list)),
Err(_) => std::ptr::null_mut(),
}
}
#[no_mangle]
@@ -1075,51 +1084,65 @@ pub unsafe fn dc_array_is_independent(
// dc_chatlist_t
#[no_mangle]
pub type dc_chatlist_t<'a> = dc_chatlist::dc_chatlist_t<'a>;
pub type dc_chatlist_t<'a> = chatlist::Chatlist<'a>;
#[no_mangle]
pub unsafe extern "C" fn dc_chatlist_unref(chatlist: *mut dc_chatlist::dc_chatlist_t) {
dc_chatlist::dc_chatlist_unref(chatlist)
pub unsafe extern "C" fn dc_chatlist_unref(chatlist: *mut dc_chatlist_t) {
assert!(!chatlist.is_null());
Box::from_raw(chatlist);
}
#[no_mangle]
pub unsafe extern "C" fn dc_chatlist_get_cnt(
chatlist: *mut dc_chatlist::dc_chatlist_t,
) -> libc::size_t {
dc_chatlist::dc_chatlist_get_cnt(chatlist)
pub unsafe extern "C" fn dc_chatlist_get_cnt(chatlist: *mut dc_chatlist_t) -> libc::size_t {
assert!(!chatlist.is_null());
let list = &*chatlist;
list.len() as libc::size_t
}
#[no_mangle]
pub unsafe extern "C" fn dc_chatlist_get_chat_id(
chatlist: *mut dc_chatlist::dc_chatlist_t,
chatlist: *mut dc_chatlist_t,
index: libc::size_t,
) -> u32 {
dc_chatlist::dc_chatlist_get_chat_id(chatlist, index)
assert!(!chatlist.is_null());
let list = &*chatlist;
list.get_chat_id(index as usize)
}
#[no_mangle]
pub unsafe extern "C" fn dc_chatlist_get_msg_id(
chatlist: *mut dc_chatlist::dc_chatlist_t,
chatlist: *mut dc_chatlist_t,
index: libc::size_t,
) -> u32 {
dc_chatlist::dc_chatlist_get_msg_id(chatlist, index)
assert!(!chatlist.is_null());
let list = &*chatlist;
list.get_msg_id(index as usize)
}
#[no_mangle]
pub unsafe extern "C" fn dc_chatlist_get_summary<'a>(
chatlist: *mut dc_chatlist::dc_chatlist_t<'a>,
chatlist: *mut dc_chatlist_t<'a>,
index: libc::size_t,
chat: *mut dc_chat_t<'a>,
) -> *mut dc_lot::dc_lot_t {
dc_chatlist::dc_chatlist_get_summary(chatlist, index, chat)
assert!(!chatlist.is_null());
let list = &*chatlist;
list.get_summary(index as usize, chat)
}
#[no_mangle]
pub unsafe extern "C" fn dc_chatlist_get_context(
chatlist: *mut dc_chatlist::dc_chatlist_t,
chatlist: *mut dc_chatlist_t,
) -> *const dc_context_t {
assert!(!chatlist.is_null());
(*chatlist).context as *const _
let list = &*chatlist;
list.get_context() as *const _
}
// dc_chat_t

View File

@@ -1,11 +1,11 @@
use std::str::FromStr;
use deltachat::chatlist::*;
use deltachat::config;
use deltachat::constants::*;
use deltachat::context::*;
use deltachat::dc_array::*;
use deltachat::dc_chat::*;
use deltachat::dc_chatlist::*;
use deltachat::dc_configure::*;
use deltachat::dc_contact::*;
use deltachat::dc_imex::*;
@@ -630,11 +630,10 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
}
"listchats" | "listarchived" | "chats" => {
let listflags = if arg0 == "listarchived" { 0x01 } else { 0 };
let chatlist = dc_get_chatlist(context, listflags, arg1_c, 0 as uint32_t);
ensure!(!chatlist.is_null(), "Failed to retrieve chatlist");
let chatlist = Chatlist::try_load(context, listflags, Some(arg1), None)?;
let mut i: libc::c_int;
let cnt = dc_chatlist_get_cnt(chatlist) as libc::c_int;
let mut i: usize;
let cnt = chatlist.len();
if cnt > 0 {
info!(
context, 0,
@@ -643,8 +642,8 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
i = cnt - 1;
while i >= 0 {
let chat = dc_get_chat(context, dc_chatlist_get_chat_id(chatlist, i as size_t));
while i > 0 {
let chat = dc_get_chat(context, chatlist.get_chat_id(i));
let temp_subtitle = dc_chat_get_subtitle(chat);
let temp_name = dc_chat_get_name(chat);
info!(
@@ -659,7 +658,7 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
);
free(temp_subtitle as *mut libc::c_void);
free(temp_name as *mut libc::c_void);
let lot = dc_chatlist_get_summary(chatlist, i as size_t, chat);
let lot = chatlist.get_summary(i, chat);
let statestr = if 0 != dc_chat_get_archived(chat) {
" [Archived]"
} else {
@@ -706,7 +705,6 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
info!(context, 0, "Location streaming enabled.");
}
println!("{} chats", cnt);
dc_chatlist_unref(chatlist);
}
"chat" => {
if sel_chat.is_null() && arg1.is_empty() {
@@ -1136,8 +1134,8 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
res += as_str(encrinfo);
free(encrinfo as *mut libc::c_void);
let chatlist = dc_get_chatlist(context, 0, 0 as *const libc::c_char, contact_id);
let chatlist_cnt = dc_chatlist_get_cnt(chatlist) as libc::c_int;
let chatlist = Chatlist::try_load(context, 0, None, Some(contact_id))?;
let chatlist_cnt = chatlist.len();
if chatlist_cnt > 0 {
res += &format!(
"\n\n{} chats shared with Contact#{}: ",
@@ -1147,12 +1145,12 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
if 0 != i {
res += ", ";
}
let chat = dc_get_chat(context, dc_chatlist_get_chat_id(chatlist, i as size_t));
let chat = dc_get_chat(context, chatlist.get_chat_id(i));
res += &format!("{}#{}", chat_prefix(chat), dc_chat_get_id(chat));
dc_chat_unref(chat);
}
}
dc_chatlist_unref(chatlist);
println!("{}", res);
}
"delcontact" => {

View File

@@ -5,11 +5,11 @@ use std::sync::{Arc, RwLock};
use std::{thread, time};
use tempfile::tempdir;
use deltachat::chatlist::*;
use deltachat::config;
use deltachat::constants::Event;
use deltachat::context::*;
use deltachat::dc_chat::*;
use deltachat::dc_chatlist::*;
use deltachat::dc_configure::*;
use deltachat::dc_contact::*;
use deltachat::dc_job::{
@@ -101,10 +101,10 @@ fn main() {
dc_send_text_msg(&ctx, chat_id, msg_text.as_ptr());
println!("fetching chats..");
let chats = dc_get_chatlist(&ctx, 0, std::ptr::null(), 0);
let chats = Chatlist::try_load(&ctx, 0, None, None).unwrap();
for i in 0..dc_chatlist_get_cnt(chats) {
let summary = dc_chatlist_get_summary(chats, 0, std::ptr::null_mut());
for i in 0..chats.len() {
let summary = chats.get_summary(0, std::ptr::null_mut());
let text1 = dc_lot_get_text1(summary);
let text2 = dc_lot_get_text2(summary);
@@ -121,7 +121,6 @@ fn main() {
println!("chat: {} - {:?} - {:?}", i, text1_s, text2_s,);
dc_lot_unref(summary);
}
dc_chatlist_unref(chats);
thread::sleep(duration);

336
src/chatlist.rs Normal file
View File

@@ -0,0 +1,336 @@
use crate::constants::*;
use crate::context::*;
use crate::dc_chat::*;
use crate::dc_contact::*;
use crate::dc_lot::*;
use crate::dc_msg::*;
use crate::dc_tools::*;
use crate::error::Result;
use crate::stock::StockMessage;
/// An object representing a single chatlist in memory.
///
/// Chatlist objects contain chat IDs and, if possible, message IDs belonging to them.
/// The chatlist object is not updated; if you want an update, you have to recreate the object.
///
/// For a **typical chat overview**, the idea is to get the list of all chats via dc_get_chatlist()
/// without any listflags (see below) and to implement a "virtual list" or so
/// (the count of chats is known by chatlist.len()).
///
/// Only for the items that are in view (the list may have several hundreds chats),
/// the UI should call chatlist.get_summary() then.
/// chatlist.get_summary() provides all elements needed for painting the item.
///
/// On a click of such an item, the UI should change to the chat view
/// and get all messages from this view via dc_get_chat_msgs().
/// Again, a "virtual list" is created (the count of messages is known)
/// and for each messages that is scrolled into view, dc_get_msg() is called then.
///
/// Why no listflags?
/// Without listflags, dc_get_chatlist() adds the deaddrop and the archive "link" automatically as needed.
/// The UI can just render these items differently then. Although the deaddrop link is currently always the
/// first entry and only present on new messages, there is the rough idea that it can be optionally always
/// present and sorted into the list by date. Rendering the deaddrop in the described way
/// would not add extra work in the UI then.
pub struct Chatlist<'a> {
context: &'a Context,
/// Stores pairs of `chat_id, message_id`
ids: Vec<(u32, u32)>,
}
impl<'a> Chatlist<'a> {
pub fn get_context(&self) -> &Context {
self.context
}
/// Get a list of chats.
/// The list can be filtered by query parameters.
///
/// The list is already sorted and starts with the most recent chat in use.
/// The sorting takes care of invalid sending dates, drafts and chats without messages.
/// Clients should not try to re-sort the list as this would be an expensive action
/// and would result in inconsistencies between clients.
///
/// To get information about each entry, use eg. chatlist.get_summary().
///
/// By default, the function adds some special entries to the list.
/// These special entries can be identified by the ID returned by chatlist.get_chat_id():
/// - DC_CHAT_ID_DEADDROP (1) - this special chat is present if there are
/// messages from addresses that have no relationship to the configured account.
/// The last of these messages is represented by DC_CHAT_ID_DEADDROP and you can retrieve details
/// about it with chatlist.get_msg_id(). Typically, the UI asks the user "Do you want to chat with NAME?"
/// and offers the options "Yes" (call dc_create_chat_by_msg_id()), "Never" (call dc_block_contact())
/// 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
/// "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.
/// - DC_CHAT_ID_ALLDONE_HINT (7) - this special chat is present
/// if DC_GCL_ADD_ALLDONE_HINT is added to listflags
/// and if there are only archived chats.
///
/// The `listflags` is a combination of flags:
/// - if the flag DC_GCL_ARCHIVED_ONLY is set, only archived chats are returned.
/// if DC_GCL_ARCHIVED_ONLY is not set, only unarchived chats are returned and
/// the pseudo-chat DC_CHAT_ID_ARCHIVED_LINK is added if there are _any_ archived
/// chats
/// - if the flag DC_GCL_NO_SPECIALS is set, deaddrop and archive link are not added
/// to the list (may be used eg. for selecting chats on forwarding, the flag is
/// not needed when DC_GCL_ARCHIVED_ONLY is already set)
/// - if the flag DC_GCL_ADD_ALLDONE_HINT is set, DC_CHAT_ID_ALLDONE_HINT
/// is added as needed.
/// `query`: An optional query for filtering the list. Only chats matching this query
/// are returned.
/// `query_contact_id`: An optional contact ID for filtering the list. Only chats including this contact ID
/// are returned.
pub fn try_load(
context: &'a Context,
listflags: usize,
query: Option<&str>,
query_contact_id: Option<u32>,
) -> Result<Self> {
let mut add_archived_link_item = 0;
// select with left join and minimum:
// - the inner select must use `hidden` and _not_ `m.hidden`
// which would refer the outer select and take a lot of time
// - `GROUP BY` is needed several messages may have the same timestamp
// - the list starts with the newest chats
// nb: the query currently shows messages from blocked contacts in groups.
// however, for normal-groups, this is okay as the message is also returned by dc_get_chat_msgs()
// (otherwise it would be hard to follow conversations, wa and tg do the same)
// for the deaddrop, however, they should really be hidden, however, _currently_ the deaddrop is not
// shown at all permanent in the chatlist.
let process_row = |row: &rusqlite::Row| {
let chat_id: i32 = row.get(0)?;
// TODO: verify that it is okay for this to be Null
let msg_id: i32 = row.get(1).unwrap_or_default();
Ok((chat_id as u32, msg_id as u32))
};
let process_rows = |rows: rusqlite::MappedRows<_>| {
rows.collect::<std::result::Result<Vec<_>, _>>()
.map_err(Into::into)
};
// nb: the query currently shows messages from blocked contacts in groups.
// however, for normal-groups, this is okay as the message is also returned by dc_get_chat_msgs()
// (otherwise it would be hard to follow conversations, wa and tg do the same)
// for the deaddrop, however, they should really be hidden, however, _currently_ the deaddrop is not
// shown at all permanent in the chatlist.
let mut ids = if let Some(query_contact_id) = query_contact_id {
// show chats shared with a given contact
context.sql.query_map(
"SELECT c.id, m.id FROM chats c LEFT JOIN msgs m \
ON c.id=m.chat_id \
AND m.timestamp=( SELECT MAX(timestamp) \
FROM msgs WHERE chat_id=c.id \
AND (hidden=0 OR (hidden=1 AND state=19))) WHERE c.id>9 \
AND c.blocked=0 AND c.id IN(SELECT chat_id FROM chats_contacts WHERE contact_id=?) \
GROUP BY c.id ORDER BY IFNULL(m.timestamp,0) DESC, m.id DESC;",
params![query_contact_id as i32],
process_row,
process_rows,
)?
} else if 0 != listflags & DC_GCL_ARCHIVED_ONLY {
// show archived chats
context.sql.query_map(
"SELECT c.id, m.id FROM chats c LEFT JOIN msgs m \
ON c.id=m.chat_id \
AND m.timestamp=( SELECT MAX(timestamp) \
FROM msgs WHERE chat_id=c.id \
AND (hidden=0 OR (hidden=1 AND state=19))) WHERE c.id>9 \
AND c.blocked=0 AND c.archived=1 GROUP BY c.id \
ORDER BY IFNULL(m.timestamp,0) DESC, m.id DESC;",
params![],
process_row,
process_rows,
)?
} else if let Some(query) = query {
let query = query.trim().to_string();
ensure!(!query.is_empty(), "missing query");
let strLikeCmd = format!("%{}%", query);
context.sql.query_map(
"SELECT c.id, m.id FROM chats c LEFT JOIN msgs m \
ON c.id=m.chat_id \
AND m.timestamp=( SELECT MAX(timestamp) \
FROM msgs WHERE chat_id=c.id \
AND (hidden=0 OR (hidden=1 AND state=19))) WHERE c.id>9 \
AND c.blocked=0 AND c.name LIKE ? \
GROUP BY c.id ORDER BY IFNULL(m.timestamp,0) DESC, m.id DESC;",
params![strLikeCmd],
process_row,
process_rows,
)?
} else {
// show normal chatlist
let mut ids = context.sql.query_map(
"SELECT c.id, m.id FROM chats c \
LEFT JOIN msgs m \
ON c.id=m.chat_id \
AND m.timestamp=( SELECT MAX(timestamp) \
FROM msgs WHERE chat_id=c.id \
AND (hidden=0 OR (hidden=1 AND state=19))) WHERE c.id>9 \
AND c.blocked=0 AND c.archived=0 \
GROUP BY c.id \
ORDER BY IFNULL(m.timestamp,0) DESC, m.id DESC;",
params![],
process_row,
process_rows,
)?;
if 0 == listflags & DC_GCL_NO_SPECIALS {
let last_deaddrop_fresh_msg_id = get_last_deaddrop_fresh_msg(context);
if last_deaddrop_fresh_msg_id > 0 {
ids.push((1, last_deaddrop_fresh_msg_id));
}
add_archived_link_item = 1;
}
ids
};
if 0 != 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 as u32, 0));
}
ids.push((DC_CHAT_ID_ARCHIVED_LINK as u32, 0));
}
Ok(Chatlist { context, ids })
}
/// Find out the number of chats.
pub fn len(&self) -> usize {
self.ids.len()
}
pub fn is_empty(&self) -> bool {
self.ids.is_empty()
}
/// 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 {
if index >= self.ids.len() {
return 0;
}
self.ids[index].0
}
/// Get a single message ID of a chatlist.
///
/// To get the message object from the message ID, use dc_get_msg().
pub fn get_msg_id(&self, index: usize) -> u32 {
if index >= self.ids.len() {
return 0;
}
self.ids[index].1
}
/// Get a summary for a chatlist index.
///
/// The summary is returned by a dc_lot_t object with the following fields:
///
/// - dc_lot_t::text1: contains the username or the strings "Me", "Draft" and so on.
/// The string may be colored by having a look at text1_meaning.
/// If there is no such name or it should not be displayed, the element is NULL.
/// - dc_lot_t::text1_meaning: one of DC_TEXT1_USERNAME, DC_TEXT1_SELF or DC_TEXT1_DRAFT.
/// Typically used to show dc_lot_t::text1 with different colors. 0 if not applicable.
/// - dc_lot_t::text2: contains an excerpt of the message text or strings as
/// "No messages". May be NULL of there is no such text (eg. for the archive link)
/// - dc_lot_t::timestamp: the timestamp of the message. 0 if not applicable.
/// - dc_lot_t::state: The state of the message as one of the DC_STATE_* constants (see #dc_msg_get_state()).
// 0 if not applicable.
pub unsafe fn get_summary(&self, index: usize, mut chat: *mut Chat<'a>) -> *mut dc_lot_t {
// The summary is created by the chat, not by the last message.
// This is because we may want to display drafts here or stuff as
// "is typing".
// Also, sth. as "No messages" would not work if the summary comes from a message.
let mut ret = dc_lot_new();
if index >= self.ids.len() {
(*ret).text2 = to_cstring("ErrBadChatlistIndex");
return ret;
}
let lastmsg_id = self.ids[index].1;
let mut lastcontact = 0 as *mut dc_contact_t;
if chat.is_null() {
chat = dc_chat_new(self.context);
let chat_to_delete = chat;
if !dc_chat_load_from_db(chat, self.ids[index].0) {
(*ret).text2 = to_cstring("ErrCannotReadChat");
dc_chat_unref(chat_to_delete);
return ret;
}
}
let lastmsg = if 0 != lastmsg_id {
let lastmsg = dc_msg_new_untyped(self.context);
dc_msg_load_from_db(lastmsg, self.context, lastmsg_id);
if (*lastmsg).from_id != 1 as libc::c_uint
&& ((*chat).type_0 == DC_CHAT_TYPE_GROUP
|| (*chat).type_0 == DC_CHAT_TYPE_VERIFIED_GROUP)
{
lastcontact = dc_contact_new(self.context);
dc_contact_load_from_db(lastcontact, &self.context.sql, (*lastmsg).from_id);
}
lastmsg
} else {
std::ptr::null_mut()
};
if (*chat).id == DC_CHAT_ID_ARCHIVED_LINK as u32 {
(*ret).text2 = dc_strdup(0 as *const libc::c_char)
} else if lastmsg.is_null() || (*lastmsg).from_id == DC_CONTACT_ID_SELF as u32 {
(*ret).text2 = to_cstring(self.context.stock_str(StockMessage::NoMessages));
} else {
dc_lot_fill(ret, lastmsg, chat, lastcontact, self.context);
}
dc_msg_unref(lastmsg);
dc_contact_unref(lastcontact);
ret
}
}
pub fn dc_get_archived_cnt(context: &Context) -> u32 {
context
.sql
.query_row_col(
context,
"SELECT COUNT(*) FROM chats WHERE blocked=0 AND archived=1;",
params![],
0,
)
.unwrap_or_default()
}
fn get_last_deaddrop_fresh_msg(context: &Context) -> u32 {
// We have an index over the state-column, this should be sufficient as there are typically
// only few fresh messages.
context
.sql
.query_row_col(
context,
"SELECT m.id FROM msgs m LEFT JOIN chats c ON c.id=m.chat_id \
WHERE m.state=10 \
AND m.hidden=0 \
AND c.blocked=2 \
ORDER BY m.timestamp DESC, m.id DESC;",
params![],
0,
)
.unwrap_or_default()
}

View File

@@ -1,9 +1,9 @@
use std::ffi::CString;
use crate::chatlist::*;
use crate::constants::*;
use crate::context::Context;
use crate::dc_array::*;
use crate::dc_chatlist::*;
use crate::dc_contact::*;
use crate::dc_job::*;
use crate::dc_msg::*;

View File

@@ -1,390 +0,0 @@
use crate::context::*;
use crate::dc_array::*;
use crate::dc_chat::*;
use crate::dc_contact::*;
use crate::dc_lot::*;
use crate::dc_msg::*;
use crate::dc_tools::*;
use crate::stock::StockMessage;
use crate::types::*;
use crate::x::*;
/* * the structure behind dc_chatlist_t */
#[derive(Copy, Clone)]
#[repr(C)]
pub struct dc_chatlist_t<'a> {
pub magic: uint32_t,
pub context: &'a Context,
pub cnt: size_t,
pub chatNlastmsg_ids: *mut dc_array_t,
}
// handle chatlists
pub unsafe fn dc_get_chatlist<'a>(
context: &'a Context,
listflags: libc::c_int,
query_str: *const libc::c_char,
query_id: uint32_t,
) -> *mut dc_chatlist_t<'a> {
let obj = dc_chatlist_new(context);
if 0 != dc_chatlist_load_from_db(obj, listflags, query_str, query_id) {
return obj;
}
dc_chatlist_unref(obj);
return 0 as *mut dc_chatlist_t;
}
/**
* @class dc_chatlist_t
*
* An object representing a single chatlist in memory.
* Chatlist objects contain chat IDs
* and, if possible, message IDs belonging to them.
* The chatlist object is not updated;
* if you want an update, you have to recreate the object.
*
* For a **typical chat overview**,
* the idea is to get the list of all chats via dc_get_chatlist()
* without any listflags (see below)
* and to implement a "virtual list" or so
* (the count of chats is known by dc_chatlist_get_cnt()).
*
* Only for the items that are in view
* (the list may have several hundreds chats),
* the UI should call dc_chatlist_get_summary() then.
* dc_chatlist_get_summary() provides all elements needed for painting the item.
*
* On a click of such an item,
* the UI should change to the chat view
* and get all messages from this view via dc_get_chat_msgs().
* Again, a "virtual list" is created
* (the count of messages is known)
* and for each messages that is scrolled into view, dc_get_msg() is called then.
*
* Why no listflags?
* Without listflags, dc_get_chatlist() adds the deaddrop
* and the archive "link" automatically as needed.
* The UI can just render these items differently then.
* Although the deaddrop link is currently always the first entry
* and only present on new messages,
* there is the rough idea that it can be optionally always present
* and sorted into the list by date.
* Rendering the deaddrop in the described way
* would not add extra work in the UI then.
*/
pub unsafe fn dc_chatlist_new(context: &Context) -> *mut dc_chatlist_t {
let mut chatlist: *mut dc_chatlist_t;
chatlist = calloc(1, ::std::mem::size_of::<dc_chatlist_t>()) as *mut dc_chatlist_t;
assert!(!chatlist.is_null());
(*chatlist).magic = 0xc4a71157u32;
(*chatlist).context = context;
(*chatlist).chatNlastmsg_ids = dc_array_new(128i32 as size_t);
assert!(!(*chatlist).chatNlastmsg_ids.is_null());
chatlist
}
pub unsafe fn dc_chatlist_unref(mut chatlist: *mut dc_chatlist_t) {
if chatlist.is_null() || (*chatlist).magic != 0xc4a71157u32 {
return;
}
dc_chatlist_empty(chatlist);
dc_array_unref((*chatlist).chatNlastmsg_ids);
(*chatlist).magic = 0i32 as uint32_t;
free(chatlist as *mut libc::c_void);
}
pub unsafe fn dc_chatlist_empty(mut chatlist: *mut dc_chatlist_t) {
if chatlist.is_null() || (*chatlist).magic != 0xc4a71157u32 {
return;
}
(*chatlist).cnt = 0i32 as size_t;
dc_array_empty((*chatlist).chatNlastmsg_ids);
}
/**
* Load a chatlist from the database to the chatlist object.
*
* @private @memberof dc_chatlist_t
*/
// TODO should return bool /rtn
unsafe fn dc_chatlist_load_from_db(
mut chatlist: *mut dc_chatlist_t,
listflags: libc::c_int,
query__: *const libc::c_char,
query_contact_id: u32,
) -> libc::c_int {
if chatlist.is_null() || (*chatlist).magic != 0xc4a71157u32 {
return 0;
}
dc_chatlist_empty(chatlist);
let mut add_archived_link_item = 0;
// select with left join and minimum:
// - the inner select must use `hidden` and _not_ `m.hidden`
// which would refer the outer select and take a lot of time
// - `GROUP BY` is needed several messages may have the same timestamp
// - the list starts with the newest chats
// nb: the query currently shows messages from blocked contacts in groups.
// however, for normal-groups, this is okay as the message is also returned by dc_get_chat_msgs()
// (otherwise it would be hard to follow conversations, wa and tg do the same)
// for the deaddrop, however, they should really be hidden, however, _currently_ the deaddrop is not
// shown at all permanent in the chatlist.
let process_row = |row: &rusqlite::Row| {
let chat_id: i32 = row.get(0)?;
// TODO: verify that it is okay for this to be Null
let msg_id: i32 = row.get(1).unwrap_or_default();
Ok((chat_id, msg_id))
};
let process_rows = |rows: rusqlite::MappedRows<_>| {
for row in rows {
let (id1, id2) = row?;
dc_array_add_id((*chatlist).chatNlastmsg_ids, id1 as u32);
dc_array_add_id((*chatlist).chatNlastmsg_ids, id2 as u32);
}
Ok(())
};
// nb: the query currently shows messages from blocked contacts in groups.
// however, for normal-groups, this is okay as the message is also returned by dc_get_chat_msgs()
// (otherwise it would be hard to follow conversations, wa and tg do the same)
// for the deaddrop, however, they should really be hidden, however, _currently_ the deaddrop is not
// shown at all permanent in the chatlist.
let success = if query_contact_id != 0 {
// show chats shared with a given contact
(*chatlist).context.sql.query_map(
"SELECT c.id, m.id FROM chats c LEFT JOIN msgs m \
ON c.id=m.chat_id \
AND m.timestamp=( SELECT MAX(timestamp) \
FROM msgs WHERE chat_id=c.id \
AND (hidden=0 OR (hidden=1 AND state=19))) WHERE c.id>9 \
AND c.blocked=0 AND c.id IN(SELECT chat_id FROM chats_contacts WHERE contact_id=?) \
GROUP BY c.id ORDER BY IFNULL(m.timestamp,0) DESC, m.id DESC;",
params![query_contact_id as i32],
process_row,
process_rows,
)
} else if 0 != listflags & 0x1 {
// show archived chats
(*chatlist).context.sql.query_map(
"SELECT c.id, m.id FROM chats c LEFT JOIN msgs m \
ON c.id=m.chat_id \
AND m.timestamp=( SELECT MAX(timestamp) \
FROM msgs WHERE chat_id=c.id \
AND (hidden=0 OR (hidden=1 AND state=19))) WHERE c.id>9 \
AND c.blocked=0 AND c.archived=1 GROUP BY c.id \
ORDER BY IFNULL(m.timestamp,0) DESC, m.id DESC;",
params![],
process_row,
process_rows,
)
} else if query__.is_null() {
// show normal chatlist
if 0 == listflags & 0x2 {
let last_deaddrop_fresh_msg_id = get_last_deaddrop_fresh_msg((*chatlist).context);
if last_deaddrop_fresh_msg_id > 0 {
dc_array_add_id((*chatlist).chatNlastmsg_ids, 1);
dc_array_add_id((*chatlist).chatNlastmsg_ids, last_deaddrop_fresh_msg_id);
}
add_archived_link_item = 1;
}
(*chatlist).context.sql.query_map(
"SELECT c.id, m.id FROM chats c \
LEFT JOIN msgs m \
ON c.id=m.chat_id \
AND m.timestamp=( SELECT MAX(timestamp) \
FROM msgs WHERE chat_id=c.id \
AND (hidden=0 OR (hidden=1 AND state=19))) WHERE c.id>9 \
AND c.blocked=0 AND c.archived=0 \
GROUP BY c.id \
ORDER BY IFNULL(m.timestamp,0) DESC, m.id DESC;",
params![],
process_row,
process_rows,
)
} else {
let query = to_string(query__).trim().to_string();
if query.is_empty() {
return 1;
} else {
let strLikeCmd = format!("%{}%", query);
(*chatlist).context.sql.query_map(
"SELECT c.id, m.id FROM chats c LEFT JOIN msgs m \
ON c.id=m.chat_id \
AND m.timestamp=( SELECT MAX(timestamp) \
FROM msgs WHERE chat_id=c.id \
AND (hidden=0 OR (hidden=1 AND state=19))) WHERE c.id>9 \
AND c.blocked=0 AND c.name LIKE ? \
GROUP BY c.id ORDER BY IFNULL(m.timestamp,0) DESC, m.id DESC;",
params![strLikeCmd],
process_row,
process_rows,
)
}
};
if 0 != add_archived_link_item && dc_get_archived_cnt((*chatlist).context) > 0 {
if dc_array_get_cnt((*chatlist).chatNlastmsg_ids) == 0 && 0 != listflags & 0x4 {
dc_array_add_id((*chatlist).chatNlastmsg_ids, 7);
dc_array_add_id((*chatlist).chatNlastmsg_ids, 0);
}
dc_array_add_id((*chatlist).chatNlastmsg_ids, 6);
dc_array_add_id((*chatlist).chatNlastmsg_ids, 0);
}
(*chatlist).cnt = dc_array_get_cnt((*chatlist).chatNlastmsg_ids) / 2;
match success {
Ok(_) => 1,
Err(err) => {
error!(
(*chatlist).context,
0, "chatlist: failed to load from database: {:?}", err
);
0
}
}
}
// Context functions to work with chatlist
pub fn dc_get_archived_cnt(context: &Context) -> libc::c_int {
context
.sql
.query_row_col(
context,
"SELECT COUNT(*) FROM chats WHERE blocked=0 AND archived=1;",
params![],
0,
)
.unwrap_or_default()
}
fn get_last_deaddrop_fresh_msg(context: &Context) -> u32 {
// we have an index over the state-column, this should be sufficient as there are typically only few fresh messages
context
.sql
.query_row_col(
context,
"SELECT m.id FROM msgs m LEFT JOIN chats c ON c.id=m.chat_id \
WHERE m.state=10 \
AND m.hidden=0 \
AND c.blocked=2 \
ORDER BY m.timestamp DESC, m.id DESC;",
params![],
0,
)
.unwrap_or_default()
}
pub unsafe fn dc_chatlist_get_cnt(chatlist: *const dc_chatlist_t) -> size_t {
if chatlist.is_null() || (*chatlist).magic != 0xc4a71157u32 {
return 0i32 as size_t;
}
(*chatlist).cnt
}
pub unsafe fn dc_chatlist_get_chat_id(chatlist: *const dc_chatlist_t, index: size_t) -> uint32_t {
if chatlist.is_null()
|| (*chatlist).magic != 0xc4a71157u32
|| (*chatlist).chatNlastmsg_ids.is_null()
|| index >= (*chatlist).cnt
{
return 0i32 as uint32_t;
}
dc_array_get_id((*chatlist).chatNlastmsg_ids, index.wrapping_mul(2))
}
pub unsafe fn dc_chatlist_get_msg_id(chatlist: *const dc_chatlist_t, index: size_t) -> uint32_t {
if chatlist.is_null()
|| (*chatlist).magic != 0xc4a71157u32
|| (*chatlist).chatNlastmsg_ids.is_null()
|| index >= (*chatlist).cnt
{
return 0i32 as uint32_t;
}
dc_array_get_id(
(*chatlist).chatNlastmsg_ids,
index.wrapping_mul(2).wrapping_add(1),
)
}
pub unsafe fn dc_chatlist_get_summary<'a>(
chatlist: *const dc_chatlist_t<'a>,
index: size_t,
mut chat: *mut Chat<'a>,
) -> *mut dc_lot_t {
let current_block: u64;
/* The summary is created by the chat, not by the last message.
This is because we may want to display drafts here or stuff as
"is typing".
Also, sth. as "No messages" would not work if the summary comes from a
message. */
/* the function never returns NULL */
let mut ret: *mut dc_lot_t = dc_lot_new();
let lastmsg_id: uint32_t;
let mut lastmsg: *mut dc_msg_t = 0 as *mut dc_msg_t;
let mut lastcontact: *mut dc_contact_t = 0 as *mut dc_contact_t;
let mut chat_to_delete: *mut Chat = 0 as *mut Chat;
if chatlist.is_null() || (*chatlist).magic != 0xc4a71157u32 || index >= (*chatlist).cnt {
(*ret).text2 = dc_strdup(b"ErrBadChatlistIndex\x00" as *const u8 as *const libc::c_char)
} else {
lastmsg_id = dc_array_get_id(
(*chatlist).chatNlastmsg_ids,
index.wrapping_mul(2).wrapping_add(1),
);
if chat.is_null() {
chat = dc_chat_new((*chatlist).context);
chat_to_delete = chat;
if !dc_chat_load_from_db(
chat,
dc_array_get_id((*chatlist).chatNlastmsg_ids, index.wrapping_mul(2)),
) {
(*ret).text2 =
dc_strdup(b"ErrCannotReadChat\x00" as *const u8 as *const libc::c_char);
current_block = 3777403817673069519;
} else {
current_block = 7651349459974463963;
}
} else {
current_block = 7651349459974463963;
}
match current_block {
3777403817673069519 => {}
_ => {
if 0 != lastmsg_id {
lastmsg = dc_msg_new_untyped((*chatlist).context);
dc_msg_load_from_db(lastmsg, (*chatlist).context, lastmsg_id);
if (*lastmsg).from_id != 1i32 as libc::c_uint
&& ((*chat).type_0 == 120i32 || (*chat).type_0 == 130i32)
{
lastcontact = dc_contact_new((*chatlist).context);
dc_contact_load_from_db(
lastcontact,
&(*chatlist).context.sql,
(*lastmsg).from_id,
);
}
}
if (*chat).id == 6i32 as libc::c_uint {
(*ret).text2 = dc_strdup(0 as *const libc::c_char)
} else if lastmsg.is_null() || (*lastmsg).from_id == 0i32 as libc::c_uint {
(*ret).text2 =
to_cstring((*chatlist).context.stock_str(StockMessage::NoMessages));
} else {
dc_lot_fill(ret, lastmsg, chat, lastcontact, (*chatlist).context);
}
}
}
}
dc_msg_unref(lastmsg);
dc_contact_unref(lastcontact);
dc_chat_unref(chat_to_delete);
ret
}

View File

@@ -1,6 +1,5 @@
use std::ffi::CString;
use failure::format_err;
use mmime::mailmime_content::*;
use mmime::mmapstring::*;
use mmime::other::*;
@@ -864,7 +863,7 @@ unsafe fn import_backup(context: &Context, backup_to_import: *const libc::c_char
}
if !loop_success {
return Err(format_err!("fail").into());
return Err(format_err!("fail"));
}
Ok(())
},

View File

@@ -29,7 +29,7 @@ pub struct dc_lot_t {
* An object containing a set of values.
* The meaning of the values is defined by the function returning the object.
* Lot objects are created
* eg. by dc_chatlist_get_summary() or dc_msg_get_summary().
* eg. by chatlist.get_summary() or dc_msg_get_summary().
*
* NB: _Lot_ is used in the meaning _heap_ here.
*/

View File

@@ -4,12 +4,12 @@ use std::fs;
use std::time::SystemTime;
use chrono::{Local, TimeZone};
use failure::format_err;
use mmime::mailimf_types::*;
use rand::{thread_rng, Rng};
use crate::context::Context;
use crate::dc_array::*;
use crate::error::Error;
use crate::types::*;
use crate::x::*;
@@ -1567,7 +1567,7 @@ pub fn as_str<'a>(s: *const libc::c_char) -> &'a str {
as_str_safe(s).unwrap_or_else(|err| panic!("{}", err))
}
pub fn as_str_safe<'a>(s: *const libc::c_char) -> Result<&'a str, failure::Error> {
pub fn as_str_safe<'a>(s: *const libc::c_char) -> Result<&'a str, Error> {
assert!(!s.is_null(), "cannot be used on null pointers");
let cstr = unsafe { CStr::from_ptr(s) };

View File

@@ -16,6 +16,8 @@ pub enum Error {
SqlFailedToOpen,
#[fail(display = "{:?}", _0)]
Io(std::io::Error),
#[fail(display = "{:?}", _0)]
Message(String),
}
pub type Result<T> = std::result::Result<T, Error>;
@@ -43,3 +45,67 @@ impl From<std::io::Error> for Error {
Error::Io(err)
}
}
#[macro_export]
macro_rules! bail {
($e:expr) => {
return Err($crate::error::Error::Message($e.to_string()));
};
($fmt:expr, $($arg:tt)+) => {
return Err($crate::error::Error::Message(format!($fmt, $($arg)+)));
};
}
#[macro_export]
macro_rules! format_err {
($e:expr) => {
$crate::error::Error::Message($e.to_string());
};
($fmt:expr, $($arg:tt)+) => {
$crate::error::Error::Message(format!($fmt, $($arg)+));
};
}
#[macro_export(local_inner_macros)]
macro_rules! ensure {
($cond:expr, $e:expr) => {
if !($cond) {
bail!($e);
}
};
($cond:expr, $fmt:expr, $($arg:tt)+) => {
if !($cond) {
bail!($fmt, $($arg)+);
}
};
}
#[macro_export]
macro_rules! ensure_eq {
($left:expr, $right:expr) => ({
match (&$left, &$right) {
(left_val, right_val) => {
if !(*left_val == *right_val) {
bail!(r#"assertion failed: `(left == right)`
left: `{:?}`,
right: `{:?}`"#, left_val, right_val)
}
}
}
});
($left:expr, $right:expr,) => ({
ensure_eq!($left, $right)
});
($left:expr, $right:expr, $($arg:tt)+) => ({
match (&($left), &($right)) {
(left_val, right_val) => {
if !(*left_val == *right_val) {
bail!(r#"assertion failed: `(left == right)`
left: `{:?}`,
right: `{:?}`: {}"#, left_val, right_val,
format_args!($($arg)+))
}
}
}
});
}

View File

@@ -19,12 +19,14 @@ extern crate rusqlite;
#[macro_use]
mod log;
#[macro_use]
pub mod error;
pub mod aheader;
pub mod chatlist;
pub mod config;
pub mod constants;
pub mod context;
pub mod error;
pub mod imap;
pub mod key;
pub mod keyhistory;
@@ -40,7 +42,6 @@ pub mod x;
pub mod dc_array;
pub mod dc_chat;
pub mod dc_chatlist;
pub mod dc_configure;
pub mod dc_contact;
pub mod dc_dehtml;