mirror of
https://github.com/chatmail/core.git
synced 2026-04-06 07:32:12 +03:00
1592 lines
48 KiB
Rust
1592 lines
48 KiB
Rust
use std::ffi::CString;
|
|
|
|
use crate::constants::*;
|
|
use crate::contact::*;
|
|
use crate::context::*;
|
|
use crate::dc_chat::*;
|
|
use crate::dc_job::*;
|
|
use crate::dc_lot::dc_lot_t;
|
|
use crate::dc_lot::*;
|
|
use crate::dc_tools::*;
|
|
use crate::param::*;
|
|
use crate::pgp::*;
|
|
use crate::sql;
|
|
use crate::stock::StockMessage;
|
|
use crate::types::*;
|
|
use crate::x::*;
|
|
use std::ptr;
|
|
|
|
/* * the structure behind dc_msg_t */
|
|
#[derive(Clone)]
|
|
#[repr(C)]
|
|
pub struct dc_msg_t<'a> {
|
|
pub id: uint32_t,
|
|
pub from_id: uint32_t,
|
|
pub to_id: uint32_t,
|
|
pub chat_id: uint32_t,
|
|
pub move_state: MoveState,
|
|
pub type_0: Viewtype,
|
|
pub state: libc::c_int,
|
|
pub hidden: libc::c_int,
|
|
pub timestamp_sort: i64,
|
|
pub timestamp_sent: i64,
|
|
pub timestamp_rcvd: i64,
|
|
pub text: Option<String>,
|
|
pub context: &'a Context,
|
|
pub rfc724_mid: *mut libc::c_char,
|
|
pub in_reply_to: *mut libc::c_char,
|
|
pub server_folder: Option<String>,
|
|
pub server_uid: uint32_t,
|
|
pub is_dc_message: libc::c_int,
|
|
pub starred: libc::c_int,
|
|
pub chat_blocked: libc::c_int,
|
|
pub location_id: uint32_t,
|
|
pub param: Params,
|
|
}
|
|
|
|
// handle messages
|
|
pub unsafe fn dc_get_msg_info(context: &Context, msg_id: u32) -> *mut libc::c_char {
|
|
let msg = dc_msg_new_untyped(context);
|
|
let mut p: *mut libc::c_char;
|
|
let mut ret = String::new();
|
|
|
|
dc_msg_load_from_db(msg, context, msg_id);
|
|
|
|
let rawtxt: Option<String> = context.sql.query_row_col(
|
|
context,
|
|
"SELECT txt_raw FROM msgs WHERE id=?;",
|
|
params![msg_id as i32],
|
|
0,
|
|
);
|
|
|
|
if rawtxt.is_none() {
|
|
ret += &format!("Cannot load message #{}.", msg_id as usize);
|
|
dc_msg_unref(msg);
|
|
return ret.strdup();
|
|
}
|
|
let rawtxt = rawtxt.unwrap();
|
|
let rawtxt = dc_truncate_str(rawtxt.trim(), 100000);
|
|
|
|
let fts = dc_timestamp_to_str(dc_msg_get_timestamp(msg));
|
|
ret += &format!("Sent: {}", fts);
|
|
|
|
let name = Contact::load_from_db(context, (*msg).from_id)
|
|
.map(|contact| contact.get_name_n_addr())
|
|
.unwrap_or_default();
|
|
|
|
ret += &format!(" by {}", name);
|
|
ret += "\n";
|
|
|
|
if (*msg).from_id != DC_CONTACT_ID_SELF as libc::c_uint {
|
|
let s = dc_timestamp_to_str(if 0 != (*msg).timestamp_rcvd {
|
|
(*msg).timestamp_rcvd
|
|
} else {
|
|
(*msg).timestamp_sort
|
|
});
|
|
ret += &format!("Received: {}", &s);
|
|
ret += "\n";
|
|
}
|
|
|
|
if (*msg).from_id == 2 || (*msg).to_id == 2 {
|
|
// device-internal message, no further details needed
|
|
dc_msg_unref(msg);
|
|
return ret.strdup();
|
|
}
|
|
|
|
context
|
|
.sql
|
|
.query_map(
|
|
"SELECT contact_id, timestamp_sent FROM msgs_mdns WHERE msg_id=?;",
|
|
params![msg_id as i32],
|
|
|row| {
|
|
let contact_id: i32 = row.get(0)?;
|
|
let ts: i64 = row.get(1)?;
|
|
Ok((contact_id, ts))
|
|
},
|
|
|rows| {
|
|
for row in rows {
|
|
let (contact_id, ts) = row?;
|
|
let fts = dc_timestamp_to_str(ts);
|
|
ret += &format!("Read: {}", fts);
|
|
|
|
let name = Contact::load_from_db(context, contact_id as u32)
|
|
.map(|contact| contact.get_name_n_addr())
|
|
.unwrap_or_default();
|
|
|
|
ret += &format!(" by {}", name);
|
|
ret += "\n";
|
|
}
|
|
Ok(())
|
|
},
|
|
)
|
|
.unwrap(); // TODO: better error handling
|
|
|
|
ret += "State: ";
|
|
match (*msg).state {
|
|
DC_STATE_IN_FRESH => ret += "Fresh",
|
|
DC_STATE_IN_NOTICED => ret += "Noticed",
|
|
DC_STATE_IN_SEEN => ret += "Seen",
|
|
DC_STATE_OUT_DELIVERED => ret += "Delivered",
|
|
DC_STATE_OUT_FAILED => ret += "Failed",
|
|
DC_STATE_OUT_MDN_RCVD => ret += "Read",
|
|
DC_STATE_OUT_PENDING => ret += "Pending",
|
|
DC_STATE_OUT_PREPARING => ret += "Preparing",
|
|
_ => ret += &format!("{}", (*msg).state),
|
|
}
|
|
|
|
if dc_msg_has_location(msg) {
|
|
ret += ", Location sent";
|
|
}
|
|
|
|
let e2ee_errors = (*msg)
|
|
.param
|
|
.get_int(Param::ErroneousE2ee)
|
|
.unwrap_or_default();
|
|
|
|
if 0 != e2ee_errors {
|
|
if 0 != e2ee_errors & 0x2 {
|
|
ret += ", Encrypted, no valid signature";
|
|
}
|
|
} else if 0
|
|
!= (*msg)
|
|
.param
|
|
.get_int(Param::GuranteeE2ee)
|
|
.unwrap_or_default()
|
|
{
|
|
ret += ", Encrypted";
|
|
}
|
|
|
|
ret += "\n";
|
|
match (*msg).param.get(Param::Error) {
|
|
Some(err) => ret += &format!("Error: {}", err),
|
|
_ => {}
|
|
}
|
|
|
|
p = dc_msg_get_file(msg);
|
|
if !p.is_null() && 0 != *p.offset(0isize) as libc::c_int {
|
|
ret += &format!(
|
|
"\nFile: {}, {}, bytes\n",
|
|
as_str(p),
|
|
dc_get_filebytes(context, as_path(p)) as libc::c_int,
|
|
);
|
|
}
|
|
free(p as *mut libc::c_void);
|
|
|
|
if (*msg).type_0 != Viewtype::Text {
|
|
ret += "Type: ";
|
|
ret += &format!("{}", (*msg).type_0);
|
|
ret += "\n";
|
|
p = dc_msg_get_filemime(msg);
|
|
ret += &format!("Mimetype: {}\n", as_str(p));
|
|
free(p as *mut libc::c_void);
|
|
}
|
|
let w = (*msg).param.get_int(Param::Width).unwrap_or_default();
|
|
let h = (*msg).param.get_int(Param::Height).unwrap_or_default();
|
|
if w != 0 || h != 0 {
|
|
ret += &format!("Dimension: {} x {}\n", w, h,);
|
|
}
|
|
let duration = (*msg).param.get_int(Param::Duration).unwrap_or_default();
|
|
if duration != 0 {
|
|
ret += &format!("Duration: {} ms\n", duration,);
|
|
}
|
|
if !rawtxt.is_empty() {
|
|
ret += &format!("\n{}\n", rawtxt);
|
|
}
|
|
if !(*msg).rfc724_mid.is_null() && 0 != *(*msg).rfc724_mid.offset(0) as libc::c_int {
|
|
ret += &format!("\nMessage-ID: {}", as_str((*msg).rfc724_mid));
|
|
}
|
|
if let Some(ref server_folder) = (*msg).server_folder {
|
|
if server_folder != "" {
|
|
ret += &format!("\nLast seen as: {}/{}", server_folder, (*msg).server_uid);
|
|
}
|
|
}
|
|
|
|
dc_msg_unref(msg);
|
|
ret.strdup()
|
|
}
|
|
|
|
pub unsafe fn dc_msg_new_untyped<'a>(context: &'a Context) -> *mut dc_msg_t<'a> {
|
|
dc_msg_new(context, Viewtype::Unknown)
|
|
}
|
|
|
|
/* *
|
|
* @class dc_msg_t
|
|
*
|
|
* An object representing a single message in memory.
|
|
* The message object is not updated.
|
|
* If you want an update, you have to recreate the object.
|
|
*/
|
|
// to check if a mail was sent, use dc_msg_is_sent()
|
|
// approx. max. length returned by dc_msg_get_text()
|
|
// approx. max. length returned by dc_get_msg_info()
|
|
pub unsafe fn dc_msg_new<'a>(context: &'a Context, viewtype: Viewtype) -> *mut dc_msg_t<'a> {
|
|
let msg = dc_msg_t {
|
|
id: 0,
|
|
from_id: 0,
|
|
to_id: 0,
|
|
chat_id: 0,
|
|
move_state: MoveState::Undefined,
|
|
type_0: viewtype,
|
|
state: 0,
|
|
hidden: 0,
|
|
timestamp_sort: 0,
|
|
timestamp_sent: 0,
|
|
timestamp_rcvd: 0,
|
|
text: None,
|
|
context,
|
|
rfc724_mid: std::ptr::null_mut(),
|
|
in_reply_to: std::ptr::null_mut(),
|
|
server_folder: None,
|
|
server_uid: 0,
|
|
is_dc_message: 0,
|
|
starred: 0,
|
|
chat_blocked: 0,
|
|
location_id: 0,
|
|
param: Params::new(),
|
|
};
|
|
|
|
Box::into_raw(Box::new(msg))
|
|
}
|
|
|
|
pub unsafe fn dc_msg_unref(msg: *mut dc_msg_t) {
|
|
if msg.is_null() {
|
|
return;
|
|
}
|
|
dc_msg_empty(msg);
|
|
Box::from_raw(msg);
|
|
}
|
|
|
|
pub unsafe fn dc_msg_empty(mut msg: *mut dc_msg_t) {
|
|
if msg.is_null() {
|
|
return;
|
|
}
|
|
free((*msg).rfc724_mid as *mut libc::c_void);
|
|
(*msg).rfc724_mid = 0 as *mut libc::c_char;
|
|
free((*msg).in_reply_to as *mut libc::c_void);
|
|
(*msg).in_reply_to = 0 as *mut libc::c_char;
|
|
(*msg).param = Params::new();
|
|
(*msg).hidden = 0i32;
|
|
}
|
|
|
|
pub unsafe fn dc_msg_get_filemime(msg: *const dc_msg_t) -> *mut libc::c_char {
|
|
let mut ret = 0 as *mut libc::c_char;
|
|
|
|
if !msg.is_null() {
|
|
match (*msg).param.get(Param::MimeType) {
|
|
Some(m) => {
|
|
ret = m.strdup();
|
|
}
|
|
None => {
|
|
if let Some(file) = (*msg).param.get(Param::File) {
|
|
let file_c = CString::yolo(file);
|
|
dc_msg_guess_msgtype_from_suffix(file_c.as_ptr(), 0 as *mut Viewtype, &mut ret);
|
|
if ret.is_null() {
|
|
ret = dc_strdup(
|
|
b"application/octet-stream\x00" as *const u8 as *const libc::c_char,
|
|
)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if !ret.is_null() {
|
|
return ret;
|
|
}
|
|
|
|
dc_strdup(0 as *const libc::c_char)
|
|
}
|
|
|
|
#[allow(non_snake_case)]
|
|
pub unsafe fn dc_msg_guess_msgtype_from_suffix(
|
|
pathNfilename: *const libc::c_char,
|
|
mut ret_msgtype: *mut Viewtype,
|
|
mut ret_mime: *mut *mut libc::c_char,
|
|
) {
|
|
let mut suffix: *mut libc::c_char = 0 as *mut libc::c_char;
|
|
let mut dummy_msgtype = Viewtype::Unknown;
|
|
let mut dummy_buf: *mut libc::c_char = 0 as *mut libc::c_char;
|
|
if !pathNfilename.is_null() {
|
|
if ret_msgtype.is_null() {
|
|
ret_msgtype = &mut dummy_msgtype
|
|
}
|
|
if ret_mime.is_null() {
|
|
ret_mime = &mut dummy_buf
|
|
}
|
|
*ret_msgtype = Viewtype::Unknown;
|
|
*ret_mime = 0 as *mut libc::c_char;
|
|
suffix = dc_get_filesuffix_lc(pathNfilename);
|
|
if !suffix.is_null() {
|
|
if strcmp(suffix, b"mp3\x00" as *const u8 as *const libc::c_char) == 0i32 {
|
|
*ret_msgtype = Viewtype::Audio;
|
|
*ret_mime = dc_strdup(b"audio/mpeg\x00" as *const u8 as *const libc::c_char)
|
|
} else if strcmp(suffix, b"aac\x00" as *const u8 as *const libc::c_char) == 0i32 {
|
|
*ret_msgtype = Viewtype::Audio;
|
|
*ret_mime = dc_strdup(b"audio/aac\x00" as *const u8 as *const libc::c_char)
|
|
} else if strcmp(suffix, b"mp4\x00" as *const u8 as *const libc::c_char) == 0i32 {
|
|
*ret_msgtype = Viewtype::Video;
|
|
*ret_mime = dc_strdup(b"video/mp4\x00" as *const u8 as *const libc::c_char)
|
|
} else if strcmp(suffix, b"jpg\x00" as *const u8 as *const libc::c_char) == 0i32
|
|
|| strcmp(suffix, b"jpeg\x00" as *const u8 as *const libc::c_char) == 0i32
|
|
{
|
|
*ret_msgtype = Viewtype::Image;
|
|
*ret_mime = dc_strdup(b"image/jpeg\x00" as *const u8 as *const libc::c_char)
|
|
} else if strcmp(suffix, b"png\x00" as *const u8 as *const libc::c_char) == 0i32 {
|
|
*ret_msgtype = Viewtype::Image;
|
|
*ret_mime = dc_strdup(b"image/png\x00" as *const u8 as *const libc::c_char)
|
|
} else if strcmp(suffix, b"webp\x00" as *const u8 as *const libc::c_char) == 0i32 {
|
|
*ret_msgtype = Viewtype::Image;
|
|
*ret_mime = dc_strdup(b"image/webp\x00" as *const u8 as *const libc::c_char)
|
|
} else if strcmp(suffix, b"gif\x00" as *const u8 as *const libc::c_char) == 0i32 {
|
|
*ret_msgtype = Viewtype::Gif;
|
|
*ret_mime = dc_strdup(b"image/gif\x00" as *const u8 as *const libc::c_char)
|
|
} else if strcmp(suffix, b"vcf\x00" as *const u8 as *const libc::c_char) == 0i32
|
|
|| strcmp(suffix, b"vcard\x00" as *const u8 as *const libc::c_char) == 0i32
|
|
{
|
|
*ret_msgtype = Viewtype::File;
|
|
*ret_mime = dc_strdup(b"text/vcard\x00" as *const u8 as *const libc::c_char)
|
|
}
|
|
}
|
|
}
|
|
free(suffix as *mut libc::c_void);
|
|
free(dummy_buf as *mut libc::c_void);
|
|
}
|
|
|
|
pub unsafe fn dc_msg_get_file(msg: *const dc_msg_t) -> *mut libc::c_char {
|
|
let mut file_abs = 0 as *mut libc::c_char;
|
|
|
|
if !msg.is_null() {
|
|
if let Some(file_rel) = (*msg).param.get(Param::File) {
|
|
let file_rel_c = CString::yolo(file_rel);
|
|
file_abs = dc_get_abs_path((*msg).context, file_rel_c.as_ptr());
|
|
}
|
|
}
|
|
if !file_abs.is_null() {
|
|
file_abs
|
|
} else {
|
|
dc_strdup(0 as *const libc::c_char)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check if a message has a location bound to it.
|
|
* These messages are also returned by dc_get_locations()
|
|
* and the UI may decide to display a special icon beside such messages,
|
|
*
|
|
* @memberof dc_msg_t
|
|
* @param msg The message object.
|
|
* @return 1=Message has location bound to it, 0=No location bound to message.
|
|
*/
|
|
pub unsafe fn dc_msg_has_location(msg: *const dc_msg_t) -> bool {
|
|
if msg.is_null() {
|
|
return false;
|
|
}
|
|
|
|
((*msg).location_id != 0i32 as libc::c_uint)
|
|
}
|
|
|
|
/**
|
|
* Set any location that should be bound to the message object.
|
|
* The function is useful to add a marker to the map
|
|
* at a position different from the self-location.
|
|
* You should not call this function
|
|
* if you want to bind the current self-location to a message;
|
|
* this is done by dc_set_location() and dc_send_locations_to_chat().
|
|
*
|
|
* Typically results in the event #DC_EVENT_LOCATION_CHANGED with
|
|
* contact_id set to DC_CONTACT_ID_SELF.
|
|
*
|
|
* @memberof dc_msg_t
|
|
* @param msg The message object.
|
|
* @param latitude North-south position of the location.
|
|
* @param longitude East-west position of the location.
|
|
* @return None.
|
|
*/
|
|
pub unsafe fn dc_msg_set_location(
|
|
msg: *mut dc_msg_t,
|
|
latitude: libc::c_double,
|
|
longitude: libc::c_double,
|
|
) {
|
|
if msg.is_null() || (latitude == 0.0 && longitude == 0.0) {
|
|
return;
|
|
}
|
|
|
|
(*msg).param.set_float(Param::SetLatitude, latitude);
|
|
(*msg).param.set_float(Param::SetLongitude, longitude);
|
|
}
|
|
|
|
pub unsafe fn dc_msg_get_timestamp(msg: *const dc_msg_t) -> i64 {
|
|
if msg.is_null() {
|
|
return 0;
|
|
}
|
|
if 0 != (*msg).timestamp_sent {
|
|
(*msg).timestamp_sent
|
|
} else {
|
|
(*msg).timestamp_sort
|
|
}
|
|
}
|
|
|
|
pub fn dc_msg_load_from_db<'a>(msg: *mut dc_msg_t<'a>, context: &'a Context, id: u32) -> bool {
|
|
if msg.is_null() {
|
|
return false;
|
|
}
|
|
|
|
let res = context.sql.query_row(
|
|
"SELECT \
|
|
m.id,rfc724_mid,m.mime_in_reply_to,m.server_folder,m.server_uid,m.move_state,m.chat_id, \
|
|
m.from_id,m.to_id,m.timestamp,m.timestamp_sent,m.timestamp_rcvd, m.type,m.state,m.msgrmsg,m.txt, \
|
|
m.param,m.starred,m.hidden,m.location_id, c.blocked \
|
|
FROM msgs m \
|
|
LEFT JOIN chats c ON c.id=m.chat_id WHERE m.id=?;",
|
|
params![id as i32],
|
|
|row| {
|
|
unsafe {
|
|
(*msg).context = context;
|
|
dc_msg_empty(msg);
|
|
|
|
(*msg).id = row.get::<_, i32>(0)? as u32;
|
|
(*msg).rfc724_mid = row.get::<_, String>(1)?.strdup();
|
|
(*msg).in_reply_to = match row.get::<_, Option<String>>(2)? {
|
|
Some(s) => s.strdup(),
|
|
None => std::ptr::null_mut(),
|
|
};
|
|
(*msg).server_folder = row.get::<_, Option<String>>(3)?;
|
|
(*msg).server_uid = row.get(4)?;
|
|
(*msg).move_state = row.get(5)?;
|
|
(*msg).chat_id = row.get(6)?;
|
|
(*msg).from_id = row.get(7)?;
|
|
(*msg).to_id = row.get(8)?;
|
|
(*msg).timestamp_sort = row.get(9)?;
|
|
(*msg).timestamp_sent = row.get(10)?;
|
|
(*msg).timestamp_rcvd = row.get(11)?;
|
|
(*msg).type_0 = row.get(12)?;
|
|
(*msg).state = row.get(13)?;
|
|
(*msg).is_dc_message = row.get(14)?;
|
|
|
|
let text;
|
|
if let rusqlite::types::ValueRef::Text(buf) = row.get_raw(15) {
|
|
if let Ok(t) = String::from_utf8(buf.to_vec()) {
|
|
text = t;
|
|
} else {
|
|
warn!(context, 0, "dc_msg_load_from_db: could not get text column as non-lossy utf8 id {}", id);
|
|
text = String::from_utf8_lossy(buf).into_owned();
|
|
}
|
|
} else {
|
|
warn!(context, 0, "dc_msg_load_from_db: could not get text column for id {}", id);
|
|
text = "[ Could not read from db ]".to_string();
|
|
}
|
|
(*msg).text = Some(text);
|
|
|
|
(*msg).param = row.get::<_, String>(16)?.parse().unwrap_or_default();
|
|
(*msg).starred = row.get(17)?;
|
|
(*msg).hidden = row.get(18)?;
|
|
(*msg).location_id = row.get(19)?;
|
|
(*msg).chat_blocked = row.get::<_, Option<i32>>(20)?.unwrap_or_default();
|
|
if (*msg).chat_blocked == 2 {
|
|
if let Some(ref text) = (*msg).text {
|
|
let ptr = text.strdup();
|
|
|
|
dc_truncate_n_unwrap_str(ptr, 256, 0);
|
|
|
|
(*msg).text = Some(to_string(ptr));
|
|
free(ptr.cast());
|
|
}
|
|
};
|
|
Ok(())
|
|
}
|
|
});
|
|
|
|
if let Err(e) = res {
|
|
warn!(
|
|
context,
|
|
0, "Error in msg_load_from_db for id {} because of {}", id, e
|
|
);
|
|
return false;
|
|
}
|
|
true
|
|
}
|
|
|
|
pub unsafe fn dc_get_mime_headers(context: &Context, msg_id: uint32_t) -> *mut libc::c_char {
|
|
let headers: Option<String> = context.sql.query_row_col(
|
|
context,
|
|
"SELECT mime_headers FROM msgs WHERE id=?;",
|
|
params![msg_id as i32],
|
|
0,
|
|
);
|
|
|
|
if let Some(headers) = headers {
|
|
let h = CString::yolo(headers);
|
|
dc_strdup_keep_null(h.as_ptr())
|
|
} else {
|
|
std::ptr::null_mut()
|
|
}
|
|
}
|
|
|
|
pub unsafe fn dc_delete_msgs(context: &Context, msg_ids: *const uint32_t, msg_cnt: libc::c_int) {
|
|
if msg_ids.is_null() || msg_cnt <= 0i32 {
|
|
return;
|
|
}
|
|
let mut i: libc::c_int = 0i32;
|
|
while i < msg_cnt {
|
|
dc_update_msg_chat_id(context, *msg_ids.offset(i as isize), 3i32 as uint32_t);
|
|
dc_job_add(
|
|
context,
|
|
110,
|
|
*msg_ids.offset(i as isize) as libc::c_int,
|
|
Params::new(),
|
|
0,
|
|
);
|
|
i += 1
|
|
}
|
|
|
|
if 0 != msg_cnt {
|
|
context.call_cb(Event::MSGS_CHANGED, 0 as uintptr_t, 0 as uintptr_t);
|
|
dc_job_kill_action(context, 105);
|
|
dc_job_add(context, 105, 0, Params::new(), 10);
|
|
};
|
|
}
|
|
|
|
pub fn dc_update_msg_chat_id(context: &Context, msg_id: u32, chat_id: u32) -> bool {
|
|
sql::execute(
|
|
context,
|
|
&context.sql,
|
|
"UPDATE msgs SET chat_id=? WHERE id=?;",
|
|
params![chat_id as i32, msg_id as i32],
|
|
)
|
|
.is_ok()
|
|
}
|
|
|
|
pub fn dc_markseen_msgs(context: &Context, msg_ids: *const u32, msg_cnt: usize) -> bool {
|
|
if msg_ids.is_null() || msg_cnt <= 0 {
|
|
return false;
|
|
}
|
|
let msgs = context.sql.prepare(
|
|
"SELECT m.state, c.blocked FROM msgs m LEFT JOIN chats c ON c.id=m.chat_id WHERE m.id=? AND m.chat_id>9",
|
|
|mut stmt, _| {
|
|
let mut res = Vec::with_capacity(msg_cnt);
|
|
for i in 0..msg_cnt {
|
|
let id = unsafe { *msg_ids.offset(i as isize) };
|
|
let query_res = stmt.query_row(params![id as i32], |row| {
|
|
Ok((row.get::<_, i32>(0)?, row.get::<_, Option<i32>>(1)?.unwrap_or_default()))
|
|
});
|
|
if let Err(rusqlite::Error::QueryReturnedNoRows) = query_res {
|
|
continue;
|
|
}
|
|
let (state, blocked) = query_res?;
|
|
res.push((id, state, blocked));
|
|
}
|
|
Ok(res)
|
|
}
|
|
);
|
|
|
|
if msgs.is_err() {
|
|
warn!(context, 0, "markseen_msgs failed: {:?}", msgs);
|
|
return false;
|
|
}
|
|
let mut send_event = false;
|
|
let msgs = msgs.unwrap();
|
|
|
|
for (id, curr_state, curr_blocked) in msgs.into_iter() {
|
|
if curr_blocked == 0 {
|
|
if curr_state == 10 || curr_state == 13 {
|
|
dc_update_msg_state(context, id, DC_STATE_IN_SEEN);
|
|
info!(context, 0, "Seen message #{}.", id);
|
|
|
|
unsafe { dc_job_add(context, 130, id as i32, Params::new(), 0) };
|
|
send_event = true;
|
|
}
|
|
} else if curr_state == DC_STATE_IN_FRESH {
|
|
dc_update_msg_state(context, id, DC_STATE_IN_NOTICED);
|
|
send_event = true;
|
|
}
|
|
}
|
|
|
|
if send_event {
|
|
context.call_cb(Event::MSGS_CHANGED, 0, 0);
|
|
}
|
|
|
|
true
|
|
}
|
|
|
|
pub fn dc_update_msg_state(context: &Context, msg_id: uint32_t, state: libc::c_int) -> bool {
|
|
sql::execute(
|
|
context,
|
|
&context.sql,
|
|
"UPDATE msgs SET state=? WHERE id=?;",
|
|
params![state, msg_id as i32],
|
|
)
|
|
.is_ok()
|
|
}
|
|
|
|
pub fn dc_star_msgs(
|
|
context: &Context,
|
|
msg_ids: *const u32,
|
|
msg_cnt: libc::c_int,
|
|
star: libc::c_int,
|
|
) -> bool {
|
|
if msg_ids.is_null() || msg_cnt <= 0 || star != 0 && star != 1 {
|
|
return false;
|
|
}
|
|
context
|
|
.sql
|
|
.prepare("UPDATE msgs SET starred=? WHERE id=?;", |mut stmt, _| {
|
|
for i in 0..msg_cnt {
|
|
stmt.execute(params![star, unsafe { *msg_ids.offset(i as isize) as i32 }])?;
|
|
}
|
|
Ok(())
|
|
})
|
|
.is_ok()
|
|
}
|
|
|
|
pub unsafe fn dc_get_msg<'a>(context: &'a Context, msg_id: uint32_t) -> *mut dc_msg_t<'a> {
|
|
let mut success = false;
|
|
let obj: *mut dc_msg_t = dc_msg_new_untyped(context);
|
|
if dc_msg_load_from_db(obj, context, msg_id) {
|
|
success = true
|
|
}
|
|
|
|
if success {
|
|
obj
|
|
} else {
|
|
dc_msg_unref(obj);
|
|
0 as *mut dc_msg_t
|
|
}
|
|
}
|
|
|
|
pub unsafe fn dc_msg_get_id(msg: *const dc_msg_t) -> uint32_t {
|
|
if msg.is_null() {
|
|
return 0i32 as uint32_t;
|
|
}
|
|
|
|
(*msg).id
|
|
}
|
|
|
|
pub unsafe fn dc_msg_get_from_id(msg: *const dc_msg_t) -> uint32_t {
|
|
if msg.is_null() {
|
|
return 0i32 as uint32_t;
|
|
}
|
|
|
|
(*msg).from_id
|
|
}
|
|
|
|
pub unsafe fn dc_msg_get_chat_id(msg: *const dc_msg_t) -> uint32_t {
|
|
if msg.is_null() {
|
|
return 0i32 as uint32_t;
|
|
}
|
|
return if 0 != (*msg).chat_blocked {
|
|
1i32 as libc::c_uint
|
|
} else {
|
|
(*msg).chat_id
|
|
};
|
|
}
|
|
|
|
pub unsafe fn dc_msg_get_viewtype(msg: *const dc_msg_t) -> Viewtype {
|
|
if msg.is_null() {
|
|
return Viewtype::Unknown;
|
|
}
|
|
|
|
(*msg).type_0
|
|
}
|
|
|
|
pub unsafe fn dc_msg_get_state(msg: *const dc_msg_t) -> libc::c_int {
|
|
if msg.is_null() {
|
|
return 0i32;
|
|
}
|
|
|
|
(*msg).state
|
|
}
|
|
|
|
pub unsafe fn dc_msg_get_received_timestamp(msg: *const dc_msg_t) -> i64 {
|
|
if msg.is_null() {
|
|
return 0;
|
|
}
|
|
|
|
(*msg).timestamp_rcvd
|
|
}
|
|
|
|
pub unsafe fn dc_msg_get_sort_timestamp(msg: *const dc_msg_t) -> i64 {
|
|
if msg.is_null() {
|
|
return 0;
|
|
}
|
|
|
|
(*msg).timestamp_sort
|
|
}
|
|
|
|
pub unsafe fn dc_msg_get_text(msg: *const dc_msg_t) -> *mut libc::c_char {
|
|
if msg.is_null() {
|
|
return dc_strdup(0 as *const libc::c_char);
|
|
}
|
|
if let Some(ref text) = (*msg).text {
|
|
dc_truncate_str(text, 30000).strdup()
|
|
} else {
|
|
ptr::null_mut()
|
|
}
|
|
}
|
|
|
|
#[allow(non_snake_case)]
|
|
pub unsafe fn dc_msg_get_filename(msg: *const dc_msg_t) -> *mut libc::c_char {
|
|
let mut ret = 0 as *mut libc::c_char;
|
|
|
|
if !msg.is_null() {
|
|
if let Some(file) = (*msg).param.get(Param::File) {
|
|
let file_c = CString::yolo(file);
|
|
ret = dc_get_filename(file_c.as_ptr());
|
|
}
|
|
}
|
|
if !ret.is_null() {
|
|
ret
|
|
} else {
|
|
dc_strdup(0 as *const libc::c_char)
|
|
}
|
|
}
|
|
|
|
pub unsafe fn dc_msg_get_filebytes(msg: *const dc_msg_t) -> uint64_t {
|
|
if !msg.is_null() {
|
|
if let Some(file) = (*msg).param.get(Param::File) {
|
|
return dc_get_filebytes((*msg).context, &file);
|
|
}
|
|
}
|
|
|
|
0
|
|
}
|
|
|
|
pub unsafe fn dc_msg_get_width(msg: *const dc_msg_t) -> libc::c_int {
|
|
if msg.is_null() {
|
|
return 0;
|
|
}
|
|
|
|
(*msg).param.get_int(Param::Width).unwrap_or_default()
|
|
}
|
|
|
|
pub unsafe fn dc_msg_get_height(msg: *const dc_msg_t) -> libc::c_int {
|
|
if msg.is_null() {
|
|
return 0;
|
|
}
|
|
|
|
(*msg).param.get_int(Param::Height).unwrap_or_default()
|
|
}
|
|
|
|
pub unsafe fn dc_msg_get_duration(msg: *const dc_msg_t) -> libc::c_int {
|
|
if msg.is_null() {
|
|
return 0;
|
|
}
|
|
|
|
(*msg).param.get_int(Param::Duration).unwrap_or_default()
|
|
}
|
|
|
|
// TODO should return bool /rtn
|
|
pub unsafe fn dc_msg_get_showpadlock(msg: *const dc_msg_t) -> libc::c_int {
|
|
if msg.is_null() {
|
|
return 0;
|
|
}
|
|
if (*msg)
|
|
.param
|
|
.get_int(Param::GuranteeE2ee)
|
|
.unwrap_or_default()
|
|
!= 0
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
0
|
|
}
|
|
|
|
pub unsafe fn dc_msg_get_summary<'a>(
|
|
msg: *mut dc_msg_t<'a>,
|
|
mut chat: *const Chat<'a>,
|
|
) -> *mut dc_lot_t {
|
|
let mut ok_to_continue = true;
|
|
let ret: *mut dc_lot_t = dc_lot_new();
|
|
let mut chat_to_delete: *mut Chat = 0 as *mut Chat;
|
|
|
|
if !msg.is_null() {
|
|
if chat.is_null() {
|
|
chat_to_delete = dc_get_chat((*msg).context, (*msg).chat_id);
|
|
if chat_to_delete.is_null() {
|
|
ok_to_continue = false;
|
|
} else {
|
|
chat = chat_to_delete;
|
|
}
|
|
}
|
|
if ok_to_continue {
|
|
let contact = if (*msg).from_id != DC_CONTACT_ID_SELF as libc::c_uint
|
|
&& ((*chat).type_0 == 120 || (*chat).type_0 == 130)
|
|
{
|
|
Contact::get_by_id((*chat).context, (*msg).from_id).ok()
|
|
} else {
|
|
None
|
|
};
|
|
|
|
dc_lot_fill(ret, msg, chat, contact.as_ref(), (*msg).context);
|
|
}
|
|
}
|
|
|
|
dc_chat_unref(chat_to_delete);
|
|
|
|
ret
|
|
}
|
|
|
|
pub unsafe fn dc_msg_get_summarytext(
|
|
msg: *mut dc_msg_t,
|
|
approx_characters: libc::c_int,
|
|
) -> *mut libc::c_char {
|
|
if msg.is_null() {
|
|
return dc_strdup(0 as *const libc::c_char);
|
|
}
|
|
|
|
let msgtext_c = (*msg)
|
|
.text
|
|
.as_ref()
|
|
.map(|s| CString::yolo(String::as_str(s)));
|
|
let msgtext_ptr = msgtext_c.map_or(ptr::null(), |s| s.as_ptr());
|
|
|
|
dc_msg_get_summarytext_by_raw(
|
|
(*msg).type_0,
|
|
msgtext_ptr,
|
|
&mut (*msg).param,
|
|
approx_characters,
|
|
(*msg).context,
|
|
)
|
|
}
|
|
|
|
/* the returned value must be free()'d */
|
|
#[allow(non_snake_case)]
|
|
pub unsafe fn dc_msg_get_summarytext_by_raw(
|
|
type_0: Viewtype,
|
|
text: *const libc::c_char,
|
|
param: &mut Params,
|
|
approx_characters: libc::c_int,
|
|
context: &Context,
|
|
) -> *mut libc::c_char {
|
|
/* get a summary text, result must be free()'d, never returns NULL. */
|
|
let mut ret;
|
|
let mut prefix: *mut libc::c_char = 0 as *mut libc::c_char;
|
|
let mut pathNfilename: *mut libc::c_char = 0 as *mut libc::c_char;
|
|
let mut value: *mut libc::c_char = 0 as *mut libc::c_char;
|
|
let mut append_text: libc::c_int = 1i32;
|
|
match type_0 {
|
|
Viewtype::Image => prefix = context.stock_str(StockMessage::Image).strdup(),
|
|
Viewtype::Gif => prefix = context.stock_str(StockMessage::Gif).strdup(),
|
|
Viewtype::Video => prefix = context.stock_str(StockMessage::Video).strdup(),
|
|
Viewtype::Voice => prefix = context.stock_str(StockMessage::VoiceMessage).strdup(),
|
|
Viewtype::Audio | Viewtype::File => {
|
|
if param.get_int(Param::Cmd) == Some(6) {
|
|
prefix = context.stock_str(StockMessage::AcSetupMsgSubject).strdup();
|
|
append_text = 0i32
|
|
} else {
|
|
pathNfilename = param
|
|
.get(Param::File)
|
|
.unwrap_or_else(|| "ErrFilename")
|
|
.strdup();
|
|
value = dc_get_filename(pathNfilename);
|
|
let label = CString::new(
|
|
context
|
|
.stock_str(if type_0 == Viewtype::Audio {
|
|
StockMessage::Audio
|
|
} else {
|
|
StockMessage::File
|
|
})
|
|
.as_ref(),
|
|
)
|
|
.unwrap();
|
|
prefix = dc_mprintf(
|
|
b"%s \xe2\x80\x93 %s\x00" as *const u8 as *const libc::c_char,
|
|
label.as_ptr(),
|
|
value,
|
|
)
|
|
}
|
|
}
|
|
_ => {
|
|
if param.get_int(Param::Cmd) == Some(9) {
|
|
prefix = context.stock_str(StockMessage::Location).strdup();
|
|
append_text = 0;
|
|
}
|
|
}
|
|
}
|
|
if 0 != append_text
|
|
&& !prefix.is_null()
|
|
&& !text.is_null()
|
|
&& 0 != *text.offset(0isize) as libc::c_int
|
|
{
|
|
ret = dc_mprintf(
|
|
b"%s \xe2\x80\x93 %s\x00" as *const u8 as *const libc::c_char,
|
|
prefix,
|
|
text,
|
|
);
|
|
dc_truncate_n_unwrap_str(ret, approx_characters, 1i32);
|
|
} else if 0 != append_text && !text.is_null() && 0 != *text.offset(0isize) as libc::c_int {
|
|
ret = dc_strdup(text);
|
|
dc_truncate_n_unwrap_str(ret, approx_characters, 1i32);
|
|
} else {
|
|
ret = prefix;
|
|
prefix = 0 as *mut libc::c_char
|
|
}
|
|
free(prefix as *mut libc::c_void);
|
|
free(pathNfilename as *mut libc::c_void);
|
|
free(value as *mut libc::c_void);
|
|
if ret.is_null() {
|
|
ret = dc_strdup(0 as *const libc::c_char)
|
|
}
|
|
|
|
ret
|
|
}
|
|
|
|
pub unsafe fn dc_msg_has_deviating_timestamp(msg: *const dc_msg_t) -> libc::c_int {
|
|
let cnv_to_local = dc_gm2local_offset();
|
|
let sort_timestamp = dc_msg_get_sort_timestamp(msg) as i64 + cnv_to_local;
|
|
let send_timestamp = dc_msg_get_timestamp(msg) as i64 + cnv_to_local;
|
|
|
|
(sort_timestamp / 86400 != send_timestamp / 86400) as libc::c_int
|
|
}
|
|
|
|
// TODO should return bool /rtn
|
|
pub unsafe fn dc_msg_is_sent(msg: *const dc_msg_t) -> libc::c_int {
|
|
if msg.is_null() {
|
|
return 0;
|
|
}
|
|
if (*msg).state >= DC_STATE_OUT_DELIVERED {
|
|
1
|
|
} else {
|
|
0
|
|
}
|
|
}
|
|
|
|
pub unsafe fn dc_msg_is_starred(msg: *const dc_msg_t) -> bool {
|
|
if msg.is_null() {
|
|
return false;
|
|
}
|
|
0 != (*msg).starred
|
|
}
|
|
|
|
// TODO should return bool /rtn
|
|
pub unsafe fn dc_msg_is_forwarded(msg: *const dc_msg_t) -> libc::c_int {
|
|
if msg.is_null() {
|
|
return 0;
|
|
}
|
|
if 0 != (*msg).param.get_int(Param::Forwarded).unwrap_or_default() {
|
|
1
|
|
} else {
|
|
0
|
|
}
|
|
}
|
|
|
|
// TODO should return bool /rtn
|
|
pub unsafe fn dc_msg_is_info(msg: *const dc_msg_t) -> libc::c_int {
|
|
if msg.is_null() {
|
|
return 0;
|
|
}
|
|
let cmd = (*msg).param.get_int(Param::Cmd).unwrap_or_default();
|
|
if (*msg).from_id == 2i32 as libc::c_uint
|
|
|| (*msg).to_id == 2i32 as libc::c_uint
|
|
|| 0 != cmd && cmd != 6i32
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
0
|
|
}
|
|
|
|
// TODO should return bool /rtn
|
|
pub unsafe fn dc_msg_is_increation(msg: *const dc_msg_t) -> libc::c_int {
|
|
if msg.is_null() {
|
|
return 0;
|
|
}
|
|
|
|
if msgtype_has_file((*msg).type_0) && (*msg).state == DC_STATE_OUT_PREPARING {
|
|
1
|
|
} else {
|
|
0
|
|
}
|
|
}
|
|
|
|
pub unsafe fn dc_msg_is_setupmessage(msg: *const dc_msg_t) -> bool {
|
|
if msg.is_null() || (*msg).type_0 != Viewtype::File {
|
|
return false;
|
|
}
|
|
|
|
(*msg).param.get_int(Param::Cmd) == Some(6)
|
|
}
|
|
|
|
pub unsafe fn dc_msg_get_setupcodebegin(msg: *const dc_msg_t) -> *mut libc::c_char {
|
|
let mut filename: *mut libc::c_char = 0 as *mut libc::c_char;
|
|
let mut buf: *mut libc::c_char = 0 as *mut libc::c_char;
|
|
let mut buf_bytes: size_t = 0i32 as size_t;
|
|
// just a pointer inside buf, MUST NOT be free()'d
|
|
let mut buf_headerline: *const libc::c_char = 0 as *const libc::c_char;
|
|
// just a pointer inside buf, MUST NOT be free()'d
|
|
let mut buf_setupcodebegin: *const libc::c_char = 0 as *const libc::c_char;
|
|
let mut ret: *mut libc::c_char = 0 as *mut libc::c_char;
|
|
if dc_msg_is_setupmessage(msg) {
|
|
filename = dc_msg_get_file(msg);
|
|
if !(filename.is_null() || *filename.offset(0isize) as libc::c_int == 0i32) {
|
|
if !(0
|
|
== dc_read_file(
|
|
(*msg).context,
|
|
filename,
|
|
&mut buf as *mut *mut libc::c_char as *mut *mut libc::c_void,
|
|
&mut buf_bytes,
|
|
)
|
|
|| buf.is_null()
|
|
|| buf_bytes <= 0)
|
|
{
|
|
if dc_split_armored_data(
|
|
buf,
|
|
&mut buf_headerline,
|
|
&mut buf_setupcodebegin,
|
|
0 as *mut *const libc::c_char,
|
|
0 as *mut *const libc::c_char,
|
|
) && strcmp(
|
|
buf_headerline,
|
|
b"-----BEGIN PGP MESSAGE-----\x00" as *const u8 as *const libc::c_char,
|
|
) == 0
|
|
&& !buf_setupcodebegin.is_null()
|
|
{
|
|
ret = dc_strdup(buf_setupcodebegin)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
free(filename as *mut libc::c_void);
|
|
free(buf as *mut libc::c_void);
|
|
if !ret.is_null() {
|
|
ret
|
|
} else {
|
|
dc_strdup(0 as *const libc::c_char)
|
|
}
|
|
}
|
|
|
|
pub unsafe fn dc_msg_set_text(mut msg: *mut dc_msg_t, text: *const libc::c_char) {
|
|
if msg.is_null() {
|
|
return;
|
|
}
|
|
(*msg).text = if text.is_null() {
|
|
None
|
|
} else {
|
|
Some(to_string(text))
|
|
};
|
|
}
|
|
|
|
pub unsafe fn dc_msg_set_file(
|
|
msg: *mut dc_msg_t,
|
|
file: *const libc::c_char,
|
|
filemime: *const libc::c_char,
|
|
) {
|
|
if msg.is_null() {
|
|
return;
|
|
}
|
|
if !file.is_null() {
|
|
(*msg).param.set(Param::File, as_str(file));
|
|
}
|
|
if !filemime.is_null() {
|
|
(*msg).param.set(Param::MimeType, as_str(filemime));
|
|
}
|
|
}
|
|
|
|
pub unsafe fn dc_msg_set_dimension(msg: *mut dc_msg_t, width: libc::c_int, height: libc::c_int) {
|
|
if msg.is_null() {
|
|
return;
|
|
}
|
|
(*msg).param.set_int(Param::Width, width);
|
|
(*msg).param.set_int(Param::Height, height);
|
|
}
|
|
|
|
pub unsafe fn dc_msg_set_duration(msg: *mut dc_msg_t, duration: libc::c_int) {
|
|
if msg.is_null() {
|
|
return;
|
|
}
|
|
(*msg).param.set_int(Param::Duration, duration);
|
|
}
|
|
|
|
pub unsafe fn dc_msg_latefiling_mediasize(
|
|
msg: *mut dc_msg_t,
|
|
width: libc::c_int,
|
|
height: libc::c_int,
|
|
duration: libc::c_int,
|
|
) {
|
|
if !msg.is_null() {
|
|
if width > 0 && height > 0 {
|
|
(*msg).param.set_int(Param::Width, width);
|
|
(*msg).param.set_int(Param::Height, height);
|
|
}
|
|
if duration > 0 {
|
|
(*msg).param.set_int(Param::Duration, duration);
|
|
}
|
|
dc_msg_save_param_to_disk(msg);
|
|
};
|
|
}
|
|
|
|
pub unsafe fn dc_msg_save_param_to_disk(msg: *mut dc_msg_t) -> bool {
|
|
if msg.is_null() {
|
|
return false;
|
|
}
|
|
|
|
sql::execute(
|
|
(*msg).context,
|
|
&(*msg).context.sql,
|
|
"UPDATE msgs SET param=? WHERE id=?;",
|
|
params![(*msg).param.to_string(), (*msg).id as i32],
|
|
)
|
|
.is_ok()
|
|
}
|
|
|
|
pub unsafe fn dc_msg_new_load<'a>(context: &'a Context, msg_id: uint32_t) -> *mut dc_msg_t<'a> {
|
|
let msg = dc_msg_new_untyped(context);
|
|
dc_msg_load_from_db(msg, context, msg_id);
|
|
msg
|
|
}
|
|
|
|
pub unsafe fn dc_delete_msg_from_db(context: &Context, msg_id: uint32_t) {
|
|
let msg: *mut dc_msg_t = dc_msg_new_untyped(context);
|
|
if dc_msg_load_from_db(msg, context, msg_id) {
|
|
sql::execute(
|
|
context,
|
|
&context.sql,
|
|
"DELETE FROM msgs WHERE id=?;",
|
|
params![(*msg).id as i32],
|
|
)
|
|
.ok();
|
|
sql::execute(
|
|
context,
|
|
&context.sql,
|
|
"DELETE FROM msgs_mdns WHERE msg_id=?;",
|
|
params![(*msg).id as i32],
|
|
)
|
|
.ok();
|
|
}
|
|
dc_msg_unref(msg);
|
|
}
|
|
|
|
/* as we do not cut inside words, this results in about 32-42 characters.
|
|
Do not use too long subjects - we add a tag after the subject which gets truncated by the clients otherwise.
|
|
It should also be very clear, the subject is _not_ the whole message.
|
|
The value is also used for CC:-summaries */
|
|
|
|
// Context functions to work with messages
|
|
|
|
pub unsafe fn dc_msg_exists(context: &Context, msg_id: uint32_t) -> libc::c_int {
|
|
if msg_id <= 9 {
|
|
return 0;
|
|
}
|
|
|
|
let chat_id: Option<i32> = context.sql.query_row_col(
|
|
context,
|
|
"SELECT chat_id FROM msgs WHERE id=?;",
|
|
params![msg_id as i32],
|
|
0,
|
|
);
|
|
|
|
if let Some(chat_id) = chat_id {
|
|
if chat_id != 3 {
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
0
|
|
}
|
|
|
|
pub fn dc_update_msg_move_state(
|
|
context: &Context,
|
|
rfc724_mid: *const libc::c_char,
|
|
state: MoveState,
|
|
) -> bool {
|
|
// we update the move_state for all messages belonging to a given Message-ID
|
|
// so that the state stay intact when parts are deleted
|
|
sql::execute(
|
|
context,
|
|
&context.sql,
|
|
"UPDATE msgs SET move_state=? WHERE rfc724_mid=?;",
|
|
params![state as i32, as_str(rfc724_mid)],
|
|
)
|
|
.is_ok()
|
|
}
|
|
|
|
fn msgstate_can_fail(state: i32) -> bool {
|
|
DC_STATE_OUT_PREPARING == state
|
|
|| DC_STATE_OUT_PENDING == state
|
|
|| DC_STATE_OUT_DELIVERED == state
|
|
}
|
|
|
|
pub unsafe fn dc_set_msg_failed(context: &Context, msg_id: uint32_t, error: *const libc::c_char) {
|
|
let mut msg = dc_msg_new_untyped(context);
|
|
|
|
if dc_msg_load_from_db(msg, context, msg_id) {
|
|
if msgstate_can_fail((*msg).state) {
|
|
(*msg).state = DC_STATE_OUT_FAILED;
|
|
}
|
|
if !error.is_null() {
|
|
(*msg).param.set(Param::Error, as_str(error));
|
|
error!(context, 0, "{}", as_str(error),);
|
|
}
|
|
|
|
if sql::execute(
|
|
context,
|
|
&context.sql,
|
|
"UPDATE msgs SET state=?, param=? WHERE id=?;",
|
|
params![(*msg).state, (*msg).param.to_string(), msg_id as i32],
|
|
)
|
|
.is_ok()
|
|
{
|
|
context.call_cb(
|
|
Event::MSG_FAILED,
|
|
(*msg).chat_id as uintptr_t,
|
|
msg_id as uintptr_t,
|
|
);
|
|
}
|
|
}
|
|
|
|
dc_msg_unref(msg);
|
|
}
|
|
|
|
/* returns 1 if an event should be send */
|
|
pub unsafe fn dc_mdn_from_ext(
|
|
context: &Context,
|
|
from_id: u32,
|
|
rfc724_mid: *const libc::c_char,
|
|
timestamp_sent: i64,
|
|
ret_chat_id: *mut u32,
|
|
ret_msg_id: *mut u32,
|
|
) -> libc::c_int {
|
|
if from_id <= 9
|
|
|| rfc724_mid.is_null()
|
|
|| ret_chat_id.is_null()
|
|
|| ret_msg_id.is_null()
|
|
|| *ret_chat_id != 0
|
|
|| *ret_msg_id != 0
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
let mut read_by_all = 0;
|
|
|
|
if let Ok((msg_id, chat_id, chat_type, msg_state)) = context.sql.query_row(
|
|
"SELECT m.id, c.id, c.type, m.state FROM msgs m \
|
|
LEFT JOIN chats c ON m.chat_id=c.id \
|
|
WHERE rfc724_mid=? AND from_id=1 \
|
|
ORDER BY m.id;",
|
|
params![as_str(rfc724_mid)],
|
|
|row| {
|
|
Ok((
|
|
row.get::<_, i32>(0)?,
|
|
row.get::<_, i32>(1)?,
|
|
row.get::<_, i32>(2)?,
|
|
row.get::<_, i32>(3)?,
|
|
))
|
|
},
|
|
) {
|
|
*ret_msg_id = msg_id as u32;
|
|
*ret_chat_id = chat_id as u32;
|
|
|
|
/* if already marked as MDNS_RCVD msgstate_can_fail() returns false.
|
|
however, it is important, that ret_msg_id is set above as this
|
|
will allow the caller eg. to move the message away */
|
|
if msgstate_can_fail(msg_state) {
|
|
let mdn_already_in_table = context
|
|
.sql
|
|
.exists(
|
|
"SELECT contact_id FROM msgs_mdns WHERE msg_id=? AND contact_id=?;",
|
|
params![*ret_msg_id as i32, from_id as i32,],
|
|
)
|
|
.unwrap_or_default();
|
|
|
|
if !mdn_already_in_table {
|
|
context.sql.execute(
|
|
"INSERT INTO msgs_mdns (msg_id, contact_id, timestamp_sent) VALUES (?, ?, ?);",
|
|
params![*ret_msg_id as i32, from_id as i32, timestamp_sent],
|
|
).unwrap(); // TODO: better error handling
|
|
}
|
|
|
|
// Normal chat? that's quite easy.
|
|
if chat_type == 100 {
|
|
dc_update_msg_state(context, *ret_msg_id, DC_STATE_OUT_MDN_RCVD);
|
|
read_by_all = 1;
|
|
} else {
|
|
/* send event about new state */
|
|
let ist_cnt: i32 = context
|
|
.sql
|
|
.query_row_col(
|
|
context,
|
|
"SELECT COUNT(*) FROM msgs_mdns WHERE msg_id=?;",
|
|
params![*ret_msg_id as i32],
|
|
0,
|
|
)
|
|
.unwrap_or_default();
|
|
/*
|
|
Groupsize: Min. MDNs
|
|
|
|
1 S n/a
|
|
2 SR 1
|
|
3 SRR 2
|
|
4 SRRR 2
|
|
5 SRRRR 3
|
|
6 SRRRRR 3
|
|
|
|
(S=Sender, R=Recipient)
|
|
*/
|
|
// for rounding, SELF is already included!
|
|
let soll_cnt = (dc_get_chat_contact_cnt(context, *ret_chat_id) + 1) / 2;
|
|
if ist_cnt >= soll_cnt {
|
|
dc_update_msg_state(context, *ret_msg_id, DC_STATE_OUT_MDN_RCVD);
|
|
read_by_all = 1;
|
|
} /* else wait for more receipts */
|
|
}
|
|
}
|
|
}
|
|
|
|
read_by_all
|
|
}
|
|
|
|
/* the number of messages assigned to real chat (!=deaddrop, !=trash) */
|
|
pub fn dc_get_real_msg_cnt(context: &Context) -> libc::c_int {
|
|
match context.sql.query_row(
|
|
"SELECT COUNT(*) \
|
|
FROM msgs m LEFT JOIN chats c ON c.id=m.chat_id \
|
|
WHERE m.id>9 AND m.chat_id>9 AND c.blocked=0;",
|
|
rusqlite::NO_PARAMS,
|
|
|row| row.get(0),
|
|
) {
|
|
Ok(res) => res,
|
|
Err(err) => {
|
|
error!(context, 0, "dc_get_real_msg_cnt() failed. {}", err);
|
|
0
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn dc_get_deaddrop_msg_cnt(context: &Context) -> size_t {
|
|
match context.sql.query_row(
|
|
"SELECT COUNT(*) \
|
|
FROM msgs m LEFT JOIN chats c ON c.id=m.chat_id \
|
|
WHERE c.blocked=2;",
|
|
rusqlite::NO_PARAMS,
|
|
|row| row.get::<_, isize>(0),
|
|
) {
|
|
Ok(res) => res as size_t,
|
|
Err(err) => {
|
|
error!(context, 0, "dc_get_deaddrop_msg_cnt() failed. {}", err);
|
|
0
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn dc_rfc724_mid_cnt(context: &Context, rfc724_mid: *const libc::c_char) -> libc::c_int {
|
|
/* check the number of messages with the same rfc724_mid */
|
|
match context.sql.query_row(
|
|
"SELECT COUNT(*) FROM msgs WHERE rfc724_mid=?;",
|
|
&[as_str(rfc724_mid)],
|
|
|row| row.get(0),
|
|
) {
|
|
Ok(res) => res,
|
|
Err(err) => {
|
|
error!(context, 0, "dc_get_rfc724_mid_cnt() failed. {}", err);
|
|
0
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn dc_rfc724_mid_exists(
|
|
context: &Context,
|
|
rfc724_mid: *const libc::c_char,
|
|
ret_server_folder: *mut *mut libc::c_char,
|
|
ret_server_uid: *mut uint32_t,
|
|
) -> uint32_t {
|
|
if rfc724_mid.is_null() || unsafe { *rfc724_mid.offset(0) as libc::c_int } == 0 {
|
|
return 0;
|
|
}
|
|
match context.sql.query_row(
|
|
"SELECT server_folder, server_uid, id FROM msgs WHERE rfc724_mid=?",
|
|
&[as_str(rfc724_mid)],
|
|
|row| {
|
|
if !ret_server_folder.is_null() {
|
|
unsafe { *ret_server_folder = row.get::<_, String>(0)?.strdup() };
|
|
}
|
|
if !ret_server_uid.is_null() {
|
|
unsafe { *ret_server_uid = row.get(1)? };
|
|
}
|
|
row.get(2)
|
|
},
|
|
) {
|
|
Ok(res) => res,
|
|
Err(_err) => {
|
|
if !ret_server_folder.is_null() {
|
|
unsafe { *ret_server_folder = 0 as *mut libc::c_char };
|
|
}
|
|
if !ret_server_uid.is_null() {
|
|
unsafe { *ret_server_uid = 0 };
|
|
}
|
|
|
|
0
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn dc_update_server_uid(
|
|
context: &Context,
|
|
rfc724_mid: *const libc::c_char,
|
|
server_folder: impl AsRef<str>,
|
|
server_uid: uint32_t,
|
|
) {
|
|
match context.sql.execute(
|
|
"UPDATE msgs SET server_folder=?, server_uid=? WHERE rfc724_mid=?;",
|
|
params![server_folder.as_ref(), server_uid, as_str(rfc724_mid)],
|
|
) {
|
|
Ok(_) => {}
|
|
Err(err) => {
|
|
warn!(context, 0, "msg: failed to update server_uid: {}", err);
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use crate::test_utils as test;
|
|
use std::ffi::CStr;
|
|
|
|
#[test]
|
|
fn test_dc_msg_guess_msgtype_from_suffix() {
|
|
unsafe {
|
|
let mut type_0 = Viewtype::Unknown;
|
|
let mut mime_0: *mut libc::c_char = 0 as *mut libc::c_char;
|
|
|
|
dc_msg_guess_msgtype_from_suffix(
|
|
b"foo/bar-sth.mp3\x00" as *const u8 as *const libc::c_char,
|
|
&mut type_0,
|
|
&mut mime_0,
|
|
);
|
|
assert_eq!(type_0, Viewtype::Audio);
|
|
assert_eq!(as_str(mime_0 as *const libc::c_char), "audio/mpeg");
|
|
free(mime_0 as *mut libc::c_void);
|
|
|
|
dc_msg_guess_msgtype_from_suffix(
|
|
b"foo/bar-sth.aac\x00" as *const u8 as *const libc::c_char,
|
|
&mut type_0,
|
|
&mut mime_0,
|
|
);
|
|
assert_eq!(type_0, Viewtype::Audio);
|
|
assert_eq!(as_str(mime_0 as *const libc::c_char), "audio/aac");
|
|
free(mime_0 as *mut libc::c_void);
|
|
|
|
dc_msg_guess_msgtype_from_suffix(
|
|
b"foo/bar-sth.mp4\x00" as *const u8 as *const libc::c_char,
|
|
&mut type_0,
|
|
&mut mime_0,
|
|
);
|
|
assert_eq!(type_0, Viewtype::Video);
|
|
assert_eq!(as_str(mime_0 as *const libc::c_char), "video/mp4");
|
|
free(mime_0 as *mut libc::c_void);
|
|
|
|
dc_msg_guess_msgtype_from_suffix(
|
|
b"foo/bar-sth.jpg\x00" as *const u8 as *const libc::c_char,
|
|
&mut type_0,
|
|
&mut mime_0,
|
|
);
|
|
assert_eq!(type_0, Viewtype::Image);
|
|
assert_eq!(
|
|
CStr::from_ptr(mime_0 as *const libc::c_char)
|
|
.to_str()
|
|
.unwrap(),
|
|
"image/jpeg"
|
|
);
|
|
free(mime_0 as *mut libc::c_void);
|
|
|
|
dc_msg_guess_msgtype_from_suffix(
|
|
b"foo/bar-sth.jpeg\x00" as *const u8 as *const libc::c_char,
|
|
&mut type_0,
|
|
&mut mime_0,
|
|
);
|
|
assert_eq!(type_0, Viewtype::Image);
|
|
assert_eq!(
|
|
CStr::from_ptr(mime_0 as *const libc::c_char)
|
|
.to_str()
|
|
.unwrap(),
|
|
"image/jpeg"
|
|
);
|
|
free(mime_0 as *mut libc::c_void);
|
|
|
|
dc_msg_guess_msgtype_from_suffix(
|
|
b"foo/bar-sth.png\x00" as *const u8 as *const libc::c_char,
|
|
&mut type_0,
|
|
&mut mime_0,
|
|
);
|
|
assert_eq!(type_0, Viewtype::Image);
|
|
assert_eq!(
|
|
CStr::from_ptr(mime_0 as *const libc::c_char)
|
|
.to_str()
|
|
.unwrap(),
|
|
"image/png"
|
|
);
|
|
free(mime_0 as *mut libc::c_void);
|
|
|
|
dc_msg_guess_msgtype_from_suffix(
|
|
b"foo/bar-sth.webp\x00" as *const u8 as *const libc::c_char,
|
|
&mut type_0,
|
|
&mut mime_0,
|
|
);
|
|
assert_eq!(type_0, Viewtype::Image);
|
|
assert_eq!(
|
|
CStr::from_ptr(mime_0 as *const libc::c_char)
|
|
.to_str()
|
|
.unwrap(),
|
|
"image/webp"
|
|
);
|
|
free(mime_0 as *mut libc::c_void);
|
|
|
|
dc_msg_guess_msgtype_from_suffix(
|
|
b"foo/bar-sth.gif\x00" as *const u8 as *const libc::c_char,
|
|
&mut type_0,
|
|
&mut mime_0,
|
|
);
|
|
assert_eq!(type_0, Viewtype::Gif);
|
|
assert_eq!(
|
|
CStr::from_ptr(mime_0 as *const libc::c_char)
|
|
.to_str()
|
|
.unwrap(),
|
|
"image/gif"
|
|
);
|
|
free(mime_0 as *mut libc::c_void);
|
|
|
|
dc_msg_guess_msgtype_from_suffix(
|
|
b"foo/bar-sth.vcf\x00" as *const u8 as *const libc::c_char,
|
|
&mut type_0,
|
|
&mut mime_0,
|
|
);
|
|
assert_eq!(type_0, Viewtype::File);
|
|
assert_eq!(
|
|
CStr::from_ptr(mime_0 as *const libc::c_char)
|
|
.to_str()
|
|
.unwrap(),
|
|
"text/vcard"
|
|
);
|
|
free(mime_0 as *mut libc::c_void);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
pub fn test_prepare_message_and_send() {
|
|
use crate::config::Config;
|
|
|
|
unsafe {
|
|
let d = test::dummy_context();
|
|
let ctx = &d.ctx;
|
|
|
|
let contact =
|
|
Contact::create(ctx, "", "dest@example.com").expect("failed to create contact");
|
|
|
|
let res = ctx.set_config(Config::ConfiguredAddr, Some("self@example.com"));
|
|
assert!(res.is_ok());
|
|
|
|
let chat = dc_create_chat_by_contact_id(ctx, contact);
|
|
assert!(chat != 0);
|
|
|
|
let msg = dc_msg_new(ctx, Viewtype::Text);
|
|
assert!(!msg.is_null());
|
|
|
|
let msg_id = dc_prepare_msg(ctx, chat, msg);
|
|
assert!(msg_id != 0);
|
|
|
|
let msg2 = dc_get_msg(ctx, msg_id);
|
|
assert!(!msg2.is_null());
|
|
}
|
|
}
|
|
}
|