mirror of
https://github.com/chatmail/core.git
synced 2026-04-28 19:06:35 +03:00
refactor(chatlist): rustify
This commit is contained in:
committed by
GitHub
parent
4902310138
commit
b23ca26908
@@ -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
|
||||
|
||||
@@ -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" => {
|
||||
|
||||
@@ -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
336
src/chatlist.rs
Normal 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()
|
||||
}
|
||||
@@ -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::*;
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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(())
|
||||
},
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -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) };
|
||||
|
||||
66
src/error.rs
66
src/error.rs
@@ -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)+))
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user