Compare commits

...

2 Commits

Author SHA1 Message Date
iequidoo
d44e521814 api!: Remove msg_id (last message ID) from Chatlist::get_summary2() (#3071)
`Chatlist::get_summary2()` is used in the jsonrpc API. CFFI uses `Chatlist::get_summary()` which
logic is preserved (it uses message ids cached in the `Chatlist`).

The motivation for this change is that jsonrpc API uses `get_last_message_for_chat()` returning the
message id + `Chatlist::get_summary2()` taking the message id when building a chatlist, but if the
message is ephemeral, the id can expire and building the chatlist would fail. The solution is to
return a summary for the last message (if any) in the chat from `Chatlist::get_summary2()` so the
call to `get_last_message_for_chat()` goes away (and two SQL queries are just merged into one) and
overall the API is easier to use.

Also this change extends `struct Summary` with the message viewtype (so an extra call to
`Message::load_from_db()` goes away) and the message id in case if it's needed for any purposes
beyond building the chatlist.
2024-04-04 02:00:34 -03:00
iequidoo
81c13beba3 api: Remove dc_chatlist_get_summary2()
It isn't used anywhere.
2024-04-04 02:00:33 -03:00
13 changed files with 227 additions and 243 deletions

View File

@@ -3477,28 +3477,6 @@ uint32_t dc_chatlist_get_msg_id (const dc_chatlist_t* chatlist, siz
dc_lot_t* dc_chatlist_get_summary (const dc_chatlist_t* chatlist, size_t index, dc_chat_t* chat);
/**
* Create a chatlist summary item when the chatlist object is already unref()'d.
*
* This function is similar to dc_chatlist_get_summary(), however,
* it takes the chat ID and the message ID as returned by dc_chatlist_get_chat_id() and dc_chatlist_get_msg_id()
* as arguments. The chatlist object itself is not needed directly.
*
* This maybe useful if you convert the complete object into a different representation
* as done e.g. in the node-bindings.
* If you have access to the chatlist object in some way, using this function is not recommended,
* use dc_chatlist_get_summary() in this case instead.
*
* @memberof dc_context_t
* @param context The context object.
* @param chat_id The chat ID to get a summary for.
* @param msg_id The message ID to get a summary for.
* @return The summary as an dc_lot_t object, see dc_chatlist_get_summary() for details.
* Must be freed using dc_lot_unref(). NULL is never returned.
*/
dc_lot_t* dc_chatlist_get_summary2 (dc_context_t* context, uint32_t chat_id, uint32_t msg_id);
/**
* Helper function to get the associated context object.
*

View File

@@ -48,7 +48,6 @@ mod dc_array;
mod lot;
mod string;
use deltachat::chatlist::Chatlist;
use self::string::*;
@@ -2889,34 +2888,6 @@ pub unsafe extern "C" fn dc_chatlist_get_summary(
})
}
#[no_mangle]
pub unsafe extern "C" fn dc_chatlist_get_summary2(
context: *mut dc_context_t,
chat_id: u32,
msg_id: u32,
) -> *mut dc_lot_t {
if context.is_null() {
eprintln!("ignoring careless call to dc_chatlist_get_summary2()");
return ptr::null_mut();
}
let ctx = &*context;
let msg_id = if msg_id == 0 {
None
} else {
Some(MsgId::new(msg_id))
};
let summary = block_on(Chatlist::get_summary2(
ctx,
ChatId::new(chat_id),
msg_id,
None,
))
.context("get_summary2 failed")
.log_err(ctx)
.unwrap_or_default();
Box::into_raw(Box::new(summary.into()))
}
#[no_mangle]
pub unsafe extern "C" fn dc_chatlist_get_context(
chatlist: *mut dc_chatlist_t,

View File

@@ -1,12 +1,9 @@
use anyhow::{Context, Result};
use deltachat::chat::{get_chat_contacts, ChatVisibility};
use deltachat::chat::{Chat, ChatId};
use deltachat::chatlist::get_last_message_for_chat;
use deltachat::chatlist::Chatlist;
use deltachat::constants::*;
use deltachat::contact::{Contact, ContactId};
use deltachat::{
chat::{get_chat_contacts, ChatVisibility},
chatlist::Chatlist,
};
use num_traits::cast::ToPrimitive;
use serde::Serialize;
use typescript_type_def::TypeDef;
@@ -67,10 +64,8 @@ pub(crate) async fn get_chat_list_item_by_id(
});
}
let last_msgid = get_last_message_for_chat(ctx, chat_id).await?;
let chat = Chat::load_from_db(ctx, chat_id).await.context("chat")?;
let summary = Chatlist::get_summary2(ctx, chat_id, last_msgid, Some(&chat))
let summary = Chatlist::get_summary2(ctx, chat_id, Some(&chat))
.await
.context("summary")?;
@@ -86,15 +81,12 @@ pub(crate) async fn get_chat_list_item_by_id(
.await?
.map(|path| path.to_str().unwrap_or("invalid/path").to_owned());
let (last_updated, message_type) = match last_msgid {
Some(id) => {
let last_message = deltachat::message::Message::load_from_db(ctx, id).await?;
(
Some(last_message.get_timestamp() * 1000),
Some(last_message.get_viewtype().into()),
)
}
let (last_updated, message_type) = match summary.id {
None => (None, None),
Some(_) => (
Some(summary.timestamp * 1000),
Some(summary.viewtype.into()),
),
};
let chat_contacts = get_chat_contacts(ctx, chat_id).await?;
@@ -145,6 +137,6 @@ pub(crate) async fn get_chat_list_item_by_id(
dm_chat_contact,
was_seen_recently,
last_message_type: message_type,
last_message_id: last_msgid.map(|id| id.to_u32()),
last_message_id: summary.id.map(|id| id.to_u32()),
})
}

View File

@@ -345,13 +345,6 @@ export class Context extends EventEmitter {
return binding.dcn_get_mime_headers(this.dcn_context, Number(messageId))
}
getChatlistItemSummary(chatId: number, messageId: number) {
debug(`getChatlistItemSummary ${chatId} ${messageId}`)
return new Lot(
binding.dcn_chatlist_get_summary2(this.dcn_context, chatId, messageId)
)
}
getChatMessages(chatId: number, flags: number, marker1before: number) {
debug(`getChatMessages ${chatId} ${flags} ${marker1before}`)
return binding.dcn_get_chat_msgs(

View File

@@ -1792,28 +1792,6 @@ NAPI_METHOD(dcn_chatlist_get_summary) {
return result;
}
NAPI_METHOD(dcn_chatlist_get_summary2) {
NAPI_ARGV(3);
NAPI_DCN_CONTEXT();
NAPI_ARGV_INT32(chat_id, 1);
NAPI_ARGV_INT32(message_id, 2);
//TRACE("calling..");
dc_lot_t* summary = dc_chatlist_get_summary2(dcn_context->dc_context, chat_id, message_id);
napi_value result;
if (summary == NULL) {
NAPI_STATUS_THROWS(napi_get_null(env, &result));
} else {
NAPI_STATUS_THROWS(napi_create_external(env, summary,
finalize_lot,
NULL, &result));
}
//TRACE("done");
return result;
}
/**
* dc_contact_t
*/
@@ -3511,7 +3489,6 @@ NAPI_INIT() {
NAPI_EXPORT_FUNCTION(dcn_chatlist_get_cnt);
NAPI_EXPORT_FUNCTION(dcn_chatlist_get_msg_id);
NAPI_EXPORT_FUNCTION(dcn_chatlist_get_summary);
NAPI_EXPORT_FUNCTION(dcn_chatlist_get_summary2);
/**
* dc_contact_t

View File

@@ -39,6 +39,7 @@ use crate::receive_imf::ReceivedMsg;
use crate::smtp::send_msg_to_smtp;
use crate::sql;
use crate::stock_str;
use crate::summary::Summary;
use crate::sync::{self, Sync::*, SyncData};
use crate::tools::{
buf_compress, create_id, create_outgoing_rfc724_mid, create_smeared_timestamp,
@@ -1329,6 +1330,52 @@ impl ChatId {
Ok(())
}
/// Returns a summary for a given chat.
pub async fn get_summary(
self,
context: &Context,
chat: Option<&Chat>,
msg: Option<&Message>,
) -> Result<Summary> {
let chat_loaded: Chat;
let chat = if let Some(chat) = chat {
chat
} else {
let chat = Chat::load_from_db(context, self).await?;
chat_loaded = chat;
&chat_loaded
};
let lastcontact = if let Some(msg) = msg {
if msg.from_id == ContactId::SELF {
None
} else {
match chat.typ {
Chattype::Group | Chattype::Broadcast | Chattype::Mailinglist => {
let lastcontact = Contact::get_by_id(context, msg.from_id)
.await
.context("Loading contact failed")?;
Some(lastcontact)
}
Chattype::Single => None,
}
}
} else {
None
};
if chat.id.is_archived_link() {
Ok(Default::default())
} else if let Some(msg) = msg.filter(|msg| msg.from_id != ContactId::UNDEFINED) {
Summary::new(context, msg, chat, lastcontact.as_ref()).await
} else {
Ok(Summary {
text: stock_str::no_messages(context).await,
..Default::default()
})
}
}
/// Returns true if the chat is protected.
pub async fn is_protected(self, context: &Context) -> Result<ProtectionStatus> {
let protection_status = context

View File

@@ -8,11 +8,10 @@ use crate::constants::{
Blocked, Chattype, DC_CHAT_ID_ALLDONE_HINT, DC_CHAT_ID_ARCHIVED_LINK, DC_GCL_ADD_ALLDONE_HINT,
DC_GCL_ARCHIVED_ONLY, DC_GCL_FOR_FORWARDING, DC_GCL_NO_SPECIALS,
};
use crate::contact::{Contact, ContactId};
use crate::contact::ContactId;
use crate::context::Context;
use crate::message::{Message, MessageState, MsgId};
use crate::param::{Param, Params};
use crate::stock_str;
use crate::summary::Summary;
use crate::tools::IsNoneOrEmpty;
@@ -22,7 +21,8 @@ pub static IS_UNREAD_FILTER: Lazy<regex::Regex> =
/// An object representing a single chatlist in memory.
///
/// Chatlist objects contain chat IDs and, if possible, message IDs belonging to them.
/// Chatlist objects contain IDs of chats and, optionally, their last messages, recently updated
/// come first.
/// 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()
@@ -347,9 +347,11 @@ impl Chatlist {
Ok(*chat_id)
}
/// Get a single message ID of a chatlist.
/// Get the last message ID of a chat with index `index`.
///
/// To get the message object from the message ID, use dc_get_msg().
/// If you want to obtain the [Message] object further, it's not recommended to use as the
/// message may expire (if it's ephemeral f.e.). Better use [`Self::get_chat_id()`] +
/// [`Message::load_from_db_last_for_chat()`].
pub fn get_msg_id(&self, index: usize) -> Result<Option<MsgId>> {
let (_chat_id, msg_id) = self
.ids
@@ -373,56 +375,28 @@ impl Chatlist {
.ids
.get(index)
.context("chatlist index is out of range")?;
Chatlist::get_summary2(context, *chat_id, *lastmsg_id, chat).await
let lastmsg = if let Some(lastmsg_id) = lastmsg_id {
Some(
Message::load_from_db(context, *lastmsg_id)
.await
.with_context(|| format!("Loading message {lastmsg_id} failed"))?,
)
} else {
None
};
chat_id.get_summary(context, chat, lastmsg.as_ref()).await
}
/// Returns a summary for a given chatlist item.
/// Returns a summary for a given chat.
pub async fn get_summary2(
context: &Context,
chat_id: ChatId,
lastmsg_id: Option<MsgId>,
chat: Option<&Chat>,
) -> Result<Summary> {
let chat_loaded: Chat;
let chat = if let Some(chat) = chat {
chat
} else {
let chat = Chat::load_from_db(context, chat_id).await?;
chat_loaded = chat;
&chat_loaded
};
let (lastmsg, lastcontact) = if let Some(lastmsg_id) = lastmsg_id {
let lastmsg = Message::load_from_db(context, lastmsg_id)
.await
.context("loading message failed")?;
if lastmsg.from_id == ContactId::SELF {
(Some(lastmsg), None)
} else {
match chat.typ {
Chattype::Group | Chattype::Broadcast | Chattype::Mailinglist => {
let lastcontact = Contact::get_by_id(context, lastmsg.from_id)
.await
.context("loading contact failed")?;
(Some(lastmsg), Some(lastcontact))
}
Chattype::Single => (Some(lastmsg), None),
}
}
} else {
(None, None)
};
if chat.id.is_archived_link() {
Ok(Default::default())
} else if let Some(lastmsg) = lastmsg.filter(|msg| msg.from_id != ContactId::UNDEFINED) {
Summary::new(context, &lastmsg, chat, lastcontact.as_ref()).await
} else {
Ok(Summary {
text: stock_str::no_messages(context).await,
..Default::default()
})
}
let lastmsg = Message::load_from_db_last_for_chat(context, chat_id)
.await
.context("Loading message failed")?;
chat_id.get_summary(context, chat, lastmsg.as_ref()).await
}
/// Returns chatlist item position for the given chat ID.
@@ -431,6 +405,9 @@ impl Chatlist {
}
/// An iterator visiting all chatlist items.
///
/// See [`Self::get_msg_id()`] why it's not recommended to use returned [MsgId]-s to obtain
/// [Message] objects and what to use instead.
pub fn iter(&self) -> impl Iterator<Item = &(ChatId, Option<MsgId>)> {
self.ids.iter()
}
@@ -448,8 +425,10 @@ pub async fn get_archived_cnt(context: &Context) -> Result<usize> {
Ok(count)
}
/// Gets the last message of a chat, the message that would also be displayed in the ChatList
/// Used for passing to `deltachat::chatlist::Chatlist::get_summary2`
/// Gets the id of the last user-visible message of a chat.
///
/// See [`Chatlist::get_msg_id()`] why it's not recommended to use returned [MsgId] to obtain the
/// [Message] object and what to use instead.
pub async fn get_last_message_for_chat(
context: &Context,
chat_id: ChatId,
@@ -474,7 +453,8 @@ mod tests {
add_contact_to_chat, create_group_chat, get_chat_contacts, remove_contact_from_chat,
send_text_msg, ProtectionStatus,
};
use crate::message::Viewtype;
use crate::contact::Contact;
use crate::message::{Message, Viewtype};
use crate::receive_imf::receive_imf;
use crate::stock_str::StockMessage;
use crate::test_utils::TestContext;

View File

@@ -480,87 +480,9 @@ impl Message {
let msg = context
.sql
.query_row_optional(
concat!(
"SELECT",
" m.id AS id,",
" rfc724_mid AS rfc724mid,",
" m.mime_in_reply_to AS mime_in_reply_to,",
" m.chat_id AS chat_id,",
" m.from_id AS from_id,",
" m.to_id AS to_id,",
" m.timestamp AS timestamp,",
" m.timestamp_sent AS timestamp_sent,",
" m.timestamp_rcvd AS timestamp_rcvd,",
" m.ephemeral_timer AS ephemeral_timer,",
" m.ephemeral_timestamp AS ephemeral_timestamp,",
" m.type AS type,",
" m.state AS state,",
" m.download_state AS download_state,",
" m.error AS error,",
" m.msgrmsg AS msgrmsg,",
" m.mime_modified AS mime_modified,",
" m.txt AS txt,",
" m.subject AS subject,",
" m.param AS param,",
" m.hidden AS hidden,",
" m.location_id AS location,",
" c.blocked AS blocked",
" FROM msgs m LEFT JOIN chats c ON c.id=m.chat_id",
" WHERE m.id=?;"
),
&Self::select_query_with_filter("WHERE m.id=?"),
(id,),
|row| {
let text = match row.get_ref("txt")? {
rusqlite::types::ValueRef::Text(buf) => {
match String::from_utf8(buf.to_vec()) {
Ok(t) => t,
Err(_) => {
warn!(
context,
concat!(
"dc_msg_load_from_db: could not get ",
"text column as non-lossy utf8 id {}"
),
id
);
String::from_utf8_lossy(buf).into_owned()
}
}
}
_ => String::new(),
};
let msg = Message {
id: row.get("id")?,
rfc724_mid: row.get::<_, String>("rfc724mid")?,
in_reply_to: row
.get::<_, Option<String>>("mime_in_reply_to")?
.and_then(|in_reply_to| parse_message_id(&in_reply_to).ok()),
chat_id: row.get("chat_id")?,
from_id: row.get("from_id")?,
to_id: row.get("to_id")?,
timestamp_sort: row.get("timestamp")?,
timestamp_sent: row.get("timestamp_sent")?,
timestamp_rcvd: row.get("timestamp_rcvd")?,
ephemeral_timer: row.get("ephemeral_timer")?,
ephemeral_timestamp: row.get("ephemeral_timestamp")?,
viewtype: row.get("type")?,
state: row.get("state")?,
download_state: row.get("download_state")?,
error: Some(row.get::<_, String>("error")?)
.filter(|error| !error.is_empty()),
is_dc_message: row.get("msgrmsg")?,
mime_modified: row.get("mime_modified")?,
text,
subject: row.get("subject")?,
param: row.get::<_, String>("param")?.parse().unwrap_or_default(),
hidden: row.get("hidden")?,
location_id: row.get("location")?,
chat_blocked: row
.get::<_, Option<Blocked>>("blocked")?
.unwrap_or_default(),
};
Ok(msg)
},
|row| Self::from_row(context, row),
)
.await
.with_context(|| format!("failed to load message {id} from the database"))?;
@@ -568,6 +490,107 @@ impl Message {
Ok(msg)
}
/// Loads the last user-visible message of the chat with given `id` from the database.
pub async fn load_from_db_last_for_chat(
context: &Context,
chat_id: ChatId,
) -> Result<Option<Message>> {
let msg = context
.sql
.query_row_optional(
&Self::select_query_with_filter(concat!(
"WHERE m.chat_id=? ",
"AND (m.hidden=0 OR m.state=?) ",
"ORDER BY m.timestamp DESC, m.id DESC LIMIT 1",
)),
(chat_id, MessageState::OutDraft),
|row| Self::from_row(context, row),
)
.await
.with_context(|| {
format!("Failed to load last message for chat {chat_id} from the database")
})?;
Ok(msg)
}
fn select_query_with_filter(filter: &str) -> String {
"\
SELECT \
m.id AS id, \
rfc724_mid AS rfc724mid, \
m.mime_in_reply_to AS mime_in_reply_to, \
m.chat_id AS chat_id, \
m.from_id AS from_id, \
m.to_id AS to_id, \
m.timestamp AS timestamp, \
m.timestamp_sent AS timestamp_sent, \
m.timestamp_rcvd AS timestamp_rcvd, \
m.ephemeral_timer AS ephemeral_timer, \
m.ephemeral_timestamp AS ephemeral_timestamp, \
m.type AS type, \
m.state AS state, \
m.download_state AS download_state, \
m.error AS error, \
m.msgrmsg AS msgrmsg, \
m.mime_modified AS mime_modified, \
m.txt AS txt, \
m.subject AS subject, \
m.param AS param, \
m.hidden AS hidden, \
m.location_id AS location, \
c.blocked AS blocked \
FROM msgs m LEFT JOIN chats c ON c.id=m.chat_id "
.to_string()
+ filter
}
fn from_row(context: &Context, row: &rusqlite::Row) -> rusqlite::Result<Message> {
let text = match row.get_ref("txt")? {
rusqlite::types::ValueRef::Text(buf) => match String::from_utf8(buf.to_vec()) {
Ok(t) => t,
Err(_) => {
warn!(
context,
"Message::from_row: could not get text column as a non-lossy UTF-8",
);
String::from_utf8_lossy(buf).into_owned()
}
},
_ => String::new(),
};
let msg = Message {
id: row.get("id")?,
rfc724_mid: row.get::<_, String>("rfc724mid")?,
in_reply_to: row
.get::<_, Option<String>>("mime_in_reply_to")?
.and_then(|in_reply_to| parse_message_id(&in_reply_to).ok()),
chat_id: row.get("chat_id")?,
from_id: row.get("from_id")?,
to_id: row.get("to_id")?,
timestamp_sort: row.get("timestamp")?,
timestamp_sent: row.get("timestamp_sent")?,
timestamp_rcvd: row.get("timestamp_rcvd")?,
ephemeral_timer: row.get("ephemeral_timer")?,
ephemeral_timestamp: row.get("ephemeral_timestamp")?,
viewtype: row.get("type")?,
state: row.get("state")?,
download_state: row.get("download_state")?,
error: Some(row.get::<_, String>("error")?).filter(|error| !error.is_empty()),
is_dc_message: row.get("msgrmsg")?,
mime_modified: row.get("mime_modified")?,
text,
subject: row.get("subject")?,
param: row.get::<_, String>("param")?.parse().unwrap_or_default(),
hidden: row.get("hidden")?,
location_id: row.get("location")?,
chat_blocked: row
.get::<_, Option<Blocked>>("blocked")?
.unwrap_or_default(),
};
Ok(msg)
}
/// Returns the MIME type of an attached file if it exists.
///
/// If the MIME type is not known, the function guesses the MIME type

View File

@@ -3447,8 +3447,11 @@ On 2020-10-25, Bob wrote:
.unwrap();
let chats = Chatlist::try_load(&t.ctx, 0, None, None).await.unwrap();
let msg_id = chats.get_msg_id(0).unwrap().unwrap();
let msg = Message::load_from_db(&t.ctx, msg_id).await.unwrap();
let chat_id = chats.get_chat_id(0).unwrap();
let msg = Message::load_from_db_last_for_chat(&t.ctx, chat_id)
.await
.unwrap()
.unwrap();
assert_eq!(msg.text, "subj with important info body text");
assert_eq!(msg.viewtype, Viewtype::Image);

View File

@@ -650,9 +650,9 @@ impl Peerstate {
.await
}
};
for (chat_id, msg_id) in chats.iter() {
let timestamp_sort = if let Some(msg_id) = msg_id {
let lastmsg = Message::load_from_db(context, *msg_id).await?;
for (chat_id, _) in chats.iter() {
let lastmsg = Message::load_from_db_last_for_chat(context, *chat_id).await?;
let timestamp_sort = if let Some(lastmsg) = lastmsg {
lastmsg.timestamp_sort
} else {
context

View File

@@ -413,6 +413,7 @@ async fn test_no_from() {
let context = &t;
let chats = Chatlist::try_load(&t, 0, None, None).await.unwrap();
assert!(chats.get_chat_id(0).is_err());
assert!(chats.get_msg_id(0).is_err());
let received = receive_imf(
@@ -455,6 +456,7 @@ async fn test_no_message_id_header() {
let t = TestContext::new_alice().await;
let chats = Chatlist::try_load(&t, 0, None, None).await.unwrap();
assert!(chats.get_chat_id(0).is_err());
assert!(chats.get_msg_id(0).is_err());
let received = receive_imf(
@@ -557,8 +559,9 @@ async fn test_escaped_recipients() {
assert_eq!(contact.get_display_name(), "h2");
let chats = Chatlist::try_load(&t, 0, None, None).await.unwrap();
let msg = Message::load_from_db(&t, chats.get_msg_id(0).unwrap().unwrap())
let msg = Message::load_from_db_last_for_chat(&t, chats.get_chat_id(0).unwrap())
.await
.unwrap()
.unwrap();
assert_eq!(msg.is_dc_message, MessengerMessage::Yes);
assert_eq!(msg.text, "hello");
@@ -745,7 +748,7 @@ async fn test_parse_ndn(
.unwrap();
let chats = Chatlist::try_load(&t, 0, None, None).await.unwrap();
let msg_id = chats.get_msg_id(0).unwrap().unwrap();
let chat_id = chats.get_chat_id(0).unwrap();
// Check that the ndn would be downloaded:
let headers = mailparse::parse_mail(raw_ndn).unwrap().headers;
@@ -756,7 +759,10 @@ async fn test_parse_ndn(
);
receive_imf(&t, raw_ndn, false).await.unwrap();
let msg = Message::load_from_db(&t, msg_id).await.unwrap();
let msg = Message::load_from_db_last_for_chat(&t, chat_id)
.await
.unwrap()
.unwrap();
assert_eq!(
msg.state,
@@ -768,7 +774,7 @@ async fn test_parse_ndn(
);
assert_eq!(msg.error(), error_msg.map(|error| error.to_string()));
(t, msg_id)
(t, msg.id)
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
@@ -860,8 +866,11 @@ async fn load_imf_email(context: &Context, imf_raw: &[u8]) -> Message {
.unwrap();
receive_imf(context, imf_raw, false).await.unwrap();
let chats = Chatlist::try_load(context, 0, None, None).await.unwrap();
let msg_id = chats.get_msg_id(0).unwrap().unwrap();
Message::load_from_db(context, msg_id).await.unwrap()
let chat_id = chats.get_chat_id(0).unwrap();
Message::load_from_db_last_for_chat(context, chat_id)
.await
.unwrap()
.unwrap()
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]

View File

@@ -7,7 +7,7 @@ use crate::chat::Chat;
use crate::constants::Chattype;
use crate::contact::{Contact, ContactId};
use crate::context::Context;
use crate::message::{Message, MessageState, Viewtype};
use crate::message::{Message, MessageState, MsgId, Viewtype};
use crate::mimeparser::SystemMessage;
use crate::stock_str;
use crate::stock_str::msg_reacted;
@@ -54,6 +54,12 @@ pub struct Summary {
/// Message preview image path
pub thumbnail_path: Option<String>,
/// Message viewtype.
pub viewtype: Viewtype,
/// Message ID.
pub id: Option<MsgId>,
}
impl Summary {
@@ -79,6 +85,8 @@ impl Summary {
timestamp: msg.get_timestamp(), // message timestamp (not reaction) to make timestamps more consistent with chats ordering
state: msg.state, // message state (not reaction) - indicating if it was me sending the last message
thumbnail_path: None,
viewtype: msg.viewtype,
id: Some(msg.id),
});
}
@@ -127,6 +135,8 @@ impl Summary {
timestamp: msg.get_timestamp(),
state: msg.state,
thumbnail_path,
viewtype: msg.viewtype,
id: Some(msg.id),
})
}

View File

@@ -561,10 +561,11 @@ impl TestContext {
// The chatlist describes what you see when you open DC, a list of chats and in each of them
// the first words of the last message. To get the last message overall, we look at the chat at the top of the
// list, which has the index 0.
let msg_id = chats.get_msg_id(0).unwrap().unwrap();
Message::load_from_db(&self.ctx, msg_id)
let chat_id = chats.get_chat_id(0).unwrap();
Message::load_from_db_last_for_chat(&self.ctx, chat_id)
.await
.expect("failed to load msg")
.unwrap()
}
/// Returns the [`Contact`] for the other [`TestContext`], creating it if necessary.