mirror of
https://github.com/chatmail/core.git
synced 2026-04-17 13:36:30 +03:00
Compare commits
26 Commits
v1.154.2
...
ephemeral-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fb325be888 | ||
|
|
f3137d87c5 | ||
|
|
9d033e2ea1 | ||
|
|
cdc15ce980 | ||
|
|
f017ffa0b3 | ||
|
|
28c79516da | ||
|
|
af1be65e8b | ||
|
|
93ffcf530d | ||
|
|
028187eeaf | ||
|
|
032b387c95 | ||
|
|
5523e7ba19 | ||
|
|
b54c2a3196 | ||
|
|
42d8502531 | ||
|
|
6002f54ce1 | ||
|
|
a830d499eb | ||
|
|
49e821d029 | ||
|
|
010cf7140f | ||
|
|
e1773edf7f | ||
|
|
b4337685cf | ||
|
|
997c5ad68a | ||
|
|
67b486bfbd | ||
|
|
f5ee9c530d | ||
|
|
0189644f08 | ||
|
|
0ca3e3100e | ||
|
|
d4c4070b3f | ||
|
|
51133ce0d6 |
@@ -1481,6 +1481,16 @@ void dc_delete_chat (dc_context_t* context, uint32_t ch
|
||||
*/
|
||||
dc_array_t* dc_get_chat_contacts (dc_context_t* context, uint32_t chat_id);
|
||||
|
||||
/**
|
||||
* Get the chat's autodelete message timer.
|
||||
*
|
||||
* @memberof dc_context_t
|
||||
* @param context The context as created by dc_context_new().
|
||||
* @param chat_id The chat ID.
|
||||
*
|
||||
* @return autodelete timer value in seconds or 0 if the timer is disabled.
|
||||
*/
|
||||
uint32_t dc_get_chat_autodelete_timer (dc_context_t* context, uint32_t chat_id);
|
||||
|
||||
/**
|
||||
* Search messages containing the given query string.
|
||||
@@ -1613,6 +1623,17 @@ int dc_remove_contact_from_chat (dc_context_t* context, uint32_t ch
|
||||
*/
|
||||
int dc_set_chat_name (dc_context_t* context, uint32_t chat_id, const char* name);
|
||||
|
||||
/**
|
||||
* Set the chat's autodelete message timer.
|
||||
*
|
||||
* @memberof dc_context_t
|
||||
* @param context The context as created by dc_context_new().
|
||||
* @param chat_id The chat ID to set the autodelete message timer for.
|
||||
* @param timer The timer value in seconds or 0 to disable the timer.
|
||||
*
|
||||
* @return 1=success, 0=error
|
||||
*/
|
||||
int dc_set_chat_autodelete_timer (dc_context_t* context, uint32_t chat_id, uint32_t timer);
|
||||
|
||||
/**
|
||||
* Set group profile image.
|
||||
@@ -2861,6 +2882,7 @@ char* dc_chat_get_name (const dc_chat_t* chat);
|
||||
char* dc_chat_get_profile_image (const dc_chat_t* chat);
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Get a color for the chat.
|
||||
* For 1:1 chats, the color is calculated from the contact's email address.
|
||||
@@ -4383,6 +4405,17 @@ int64_t dc_lot_get_timestamp (const dc_lot_t* lot);
|
||||
*/
|
||||
#define DC_EVENT_MSG_READ 2015
|
||||
|
||||
/**
|
||||
* Time left until the next message deletion has changed.
|
||||
*
|
||||
* The only parameter is the number of seconds left until next message
|
||||
* deletion. It is rounded up. If the timer is 0, it means there are no
|
||||
* messages to be deleted in the future.
|
||||
*
|
||||
* @param data1 (int) Timer in seconds
|
||||
* @param data2 (int) 0
|
||||
*/
|
||||
#define DC_EVENT_MSG_DELETE_TIMEOUT_CHANGED 2016
|
||||
|
||||
/**
|
||||
* Chat changed. The name or the image of a chat group was changed or members were added or removed.
|
||||
@@ -4395,6 +4428,11 @@ int64_t dc_lot_get_timestamp (const dc_lot_t* lot);
|
||||
*/
|
||||
#define DC_EVENT_CHAT_MODIFIED 2020
|
||||
|
||||
/**
|
||||
* Chat autodelete timer changed.
|
||||
*/
|
||||
#define DC_EVENT_CHAT_AUTODELETE_TIMER_MODIFIED 2021
|
||||
|
||||
|
||||
/**
|
||||
* Contact(s) created, renamed, verified, blocked or deleted.
|
||||
|
||||
@@ -167,9 +167,20 @@ impl ContextWrapper {
|
||||
msg_id.to_u32() as uintptr_t,
|
||||
);
|
||||
}
|
||||
Event::MsgDeleteTimeoutChanged { timer } => {
|
||||
ffi_cb(self, event_id, timer as uintptr_t, 0);
|
||||
}
|
||||
Event::ChatModified(chat_id) => {
|
||||
ffi_cb(self, event_id, chat_id.to_u32() as uintptr_t, 0);
|
||||
}
|
||||
Event::ChatAutodeleteTimerModified { chat_id, timer } => {
|
||||
ffi_cb(
|
||||
self,
|
||||
event_id,
|
||||
chat_id.to_u32() as uintptr_t,
|
||||
timer as uintptr_t,
|
||||
);
|
||||
}
|
||||
Event::ContactsChanged(id) | Event::LocationChanged(id) => {
|
||||
let id = id.unwrap_or_default();
|
||||
ffi_cb(self, event_id, id as uintptr_t, 0);
|
||||
@@ -1451,6 +1462,41 @@ pub unsafe extern "C" fn dc_set_chat_mute_duration(
|
||||
.unwrap_or(0)
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn dc_get_chat_autodelete_timer(
|
||||
context: *mut dc_context_t,
|
||||
chat_id: u32,
|
||||
) -> u32 {
|
||||
if context.is_null() || chat_id <= constants::DC_CHAT_ID_LAST_SPECIAL as u32 {
|
||||
eprintln!("ignoring careless call to dc_set_chat_autodelete_timer()");
|
||||
return 0;
|
||||
}
|
||||
let ffi_context = &*context;
|
||||
ffi_context
|
||||
.with_inner(|ctx| chat::get_autodelete_timer(ctx, ChatId::new(chat_id)))
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn dc_set_chat_autodelete_timer(
|
||||
context: *mut dc_context_t,
|
||||
chat_id: u32,
|
||||
timer: u32,
|
||||
) -> libc::c_int {
|
||||
if context.is_null() || chat_id <= constants::DC_CHAT_ID_LAST_SPECIAL as u32 {
|
||||
eprintln!("ignoring careless call to dc_set_chat_autodelete_timer()");
|
||||
return 0;
|
||||
}
|
||||
let ffi_context = &*context;
|
||||
ffi_context
|
||||
.with_inner(|ctx| {
|
||||
chat::set_autodelete_timer(ctx, ChatId::new(chat_id), timer)
|
||||
.map(|_| 1)
|
||||
.unwrap_or_log_default(ctx, "Failed to set autodelete timer")
|
||||
})
|
||||
.unwrap_or(0)
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn dc_get_msg_info(
|
||||
context: *mut dc_context_t,
|
||||
|
||||
@@ -138,6 +138,22 @@ class Chat(object):
|
||||
"""
|
||||
return bool(lib.dc_chat_get_remaining_mute_duration(self.id))
|
||||
|
||||
def get_autodelete_timer(self):
|
||||
""" get autodelete timer.
|
||||
|
||||
:returns: autodelete timer value in seconds
|
||||
"""
|
||||
return lib.dc_get_chat_autodelete_timer(self._dc_context, self.id)
|
||||
|
||||
def set_autodelete_timer(self, timer):
|
||||
""" set autodelete timer.
|
||||
|
||||
:param: timer value in seconds
|
||||
|
||||
:returns: None
|
||||
"""
|
||||
return lib.dc_set_chat_autodelete_timer(self._dc_context, self.id, timer)
|
||||
|
||||
def get_type(self):
|
||||
""" (deprecated) return type of this chat.
|
||||
|
||||
|
||||
@@ -92,6 +92,7 @@ DC_EVENT_MSG_DELIVERED = 2010
|
||||
DC_EVENT_MSG_FAILED = 2012
|
||||
DC_EVENT_MSG_READ = 2015
|
||||
DC_EVENT_CHAT_MODIFIED = 2020
|
||||
DC_EVENT_CHAT_AUTODELETE_TIMER_MODIFIED = 2021
|
||||
DC_EVENT_CONTACTS_CHANGED = 2030
|
||||
DC_EVENT_LOCATION_CHANGED = 2035
|
||||
DC_EVENT_CONFIGURE_PROGRESS = 2041
|
||||
|
||||
@@ -1473,6 +1473,59 @@ class TestOnlineAccount:
|
||||
locations3 = chat2.get_locations(contact=contact)
|
||||
assert not locations3
|
||||
|
||||
def test_autodelete_timer(self, acfactory, lp):
|
||||
ac1, ac2 = acfactory.get_two_online_accounts()
|
||||
|
||||
lp.sec("ac1: create chat with ac2")
|
||||
chat1 = self.get_chat(ac1, ac2)
|
||||
chat2 = self.get_chat(ac2, ac1)
|
||||
|
||||
lp.sec("ac1: set autodelete timer to 60")
|
||||
chat1.set_autodelete_timer(60)
|
||||
|
||||
lp.sec("ac1: check that autodelete timer is set for chat")
|
||||
assert chat1.get_autodelete_timer() == 60
|
||||
chat1_summary = chat1.get_summary()
|
||||
assert chat1_summary["autodelete_timer"] == 60
|
||||
|
||||
lp.sec("ac2: receive system message about autodelete timer modification")
|
||||
ac2._evtracker.get_matching("DC_EVENT_CHAT_AUTODELETE_TIMER_MODIFIED")
|
||||
incoming_message_event1 = ac2._evtracker.get_matching("DC_EVENT_INCOMING_MSG")
|
||||
system_message1 = ac2.get_message_by_id(incoming_message_event1.data2)
|
||||
assert chat2.get_autodelete_timer() == 60
|
||||
assert system_message1.is_system_message()
|
||||
assert "Autodelete timer: 60\n" in system_message1.get_message_info()
|
||||
|
||||
lp.sec("ac2: send message to ac1")
|
||||
sent_message = chat2.send_text("message")
|
||||
assert sent_message.is_encrypted()
|
||||
assert "Autodelete timer: 60\n" in sent_message.get_message_info()
|
||||
|
||||
# Timer is started immediately for sent messages
|
||||
assert "Expires: " in sent_message.get_message_info()
|
||||
|
||||
lp.sec("ac1: waiting for message from ac2")
|
||||
incoming_message_event2 = ac1._evtracker.get_matching("DC_EVENT_INCOMING_MSG")
|
||||
text_message = ac1.get_message_by_id(incoming_message_event2.data2)
|
||||
assert text_message.text == "message"
|
||||
assert text_message.is_encrypted()
|
||||
assert "Autodelete timer: 60\n" in text_message.get_message_info()
|
||||
|
||||
# Timer should not start until message is displayed
|
||||
assert "Expires: " not in text_message.get_message_info()
|
||||
text_message.mark_seen()
|
||||
assert "Expires: " in text_message.get_message_info()
|
||||
|
||||
lp.sec("ac2: set autodelete timer to 0")
|
||||
chat2.set_autodelete_timer(0)
|
||||
|
||||
lp.sec("ac1: receive system message about autodelete timer modification")
|
||||
ac1._evtracker.get_matching("DC_EVENT_CHAT_AUTODELETE_TIMER_MODIFIED")
|
||||
incoming_message_event3 = ac1._evtracker.get_matching("DC_EVENT_INCOMING_MSG")
|
||||
system_message2 = ac1.get_message_by_id(incoming_message_event3.data2)
|
||||
assert "Autodelete timer: " not in system_message2.get_message_info()
|
||||
assert chat1.get_autodelete_timer() == 0
|
||||
|
||||
|
||||
class TestGroupStressTests:
|
||||
def test_group_many_members_add_leave_remove(self, acfactory, lp):
|
||||
|
||||
188
src/chat.rs
188
src/chat.rs
@@ -1,6 +1,6 @@
|
||||
//! # Chat module
|
||||
|
||||
use std::convert::TryFrom;
|
||||
use std::convert::{TryFrom, TryInto};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::time::{Duration, SystemTime};
|
||||
|
||||
@@ -663,6 +663,7 @@ impl Chat {
|
||||
profile_image: self.get_profile_image(context).unwrap_or_else(PathBuf::new),
|
||||
draft,
|
||||
is_muted: self.is_muted(),
|
||||
autodelete_timer: get_autodelete_timer(context, self.id),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -881,12 +882,20 @@ impl Chat {
|
||||
);
|
||||
}
|
||||
|
||||
// get autodelete timer
|
||||
let autodelete_timer = get_autodelete_timer(context, self.id);
|
||||
let autodelete_timestamp = if autodelete_timer == 0 {
|
||||
0
|
||||
} else {
|
||||
timestamp + i64::from(autodelete_timer)
|
||||
};
|
||||
|
||||
// add message to the database
|
||||
|
||||
if sql::execute(
|
||||
context,
|
||||
&context.sql,
|
||||
"INSERT INTO msgs (rfc724_mid, chat_id, from_id, to_id, timestamp, type, state, txt, param, hidden, mime_in_reply_to, mime_references, location_id) VALUES (?,?,?,?,?, ?,?,?,?,?, ?,?,?);",
|
||||
"INSERT INTO msgs (rfc724_mid, chat_id, from_id, to_id, timestamp, type, state, txt, param, hidden, mime_in_reply_to, mime_references, location_id, autodelete_timer, autodelete_timestamp) VALUES (?,?,?,?,?, ?,?,?,?,?, ?,?,?,?,?);",
|
||||
params![
|
||||
new_rfc724_mid,
|
||||
self.id,
|
||||
@@ -901,6 +910,8 @@ impl Chat {
|
||||
new_in_reply_to,
|
||||
new_references,
|
||||
location_id as i32,
|
||||
autodelete_timer,
|
||||
autodelete_timestamp
|
||||
]
|
||||
).is_ok() {
|
||||
msg_id = sql::get_rowid(
|
||||
@@ -920,6 +931,7 @@ impl Chat {
|
||||
} else {
|
||||
error!(context, "Cannot send message, not configured.",);
|
||||
}
|
||||
update_autodelete_timeout(context);
|
||||
|
||||
Ok(MsgId::new(msg_id))
|
||||
}
|
||||
@@ -1022,6 +1034,8 @@ pub struct ChatInfo {
|
||||
// - [ ] lastUpdated,
|
||||
// - [ ] freshMessageCounter,
|
||||
// - [ ] email
|
||||
/// Automatic message deletion timer.
|
||||
pub autodelete_timer: u32,
|
||||
}
|
||||
|
||||
/// Create a chat from a message ID.
|
||||
@@ -1587,45 +1601,99 @@ pub fn marknoticed_all_chats(context: &Context) -> Result<(), Error> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Emits an event to update the time until the next local deletion.
|
||||
///
|
||||
/// This takes into account only per-chat timeouts, because global device
|
||||
/// timeouts are at least one hour long and deletion is triggered often enough
|
||||
/// by user actions.
|
||||
pub fn update_autodelete_timeout(context: &Context) {
|
||||
let autodelete_timestamp: Option<i64> = match context.sql.query_get_value_result(
|
||||
"SELECT autodelete_timestamp \
|
||||
FROM msgs \
|
||||
WHERE autodelete_timestamp != 0 \
|
||||
AND chat_id != ? \
|
||||
ORDER BY autodelete_timestamp ASC \
|
||||
LIMIT 1",
|
||||
params![DC_CHAT_ID_TRASH],
|
||||
) {
|
||||
Err(err) => {
|
||||
warn!(context, "Can't calculate next autodelete timeout: {}", err);
|
||||
return;
|
||||
}
|
||||
Ok(autodelete_timestamp) => autodelete_timestamp,
|
||||
};
|
||||
|
||||
let timer = if let Some(next_autodelete) = autodelete_timestamp {
|
||||
let now = time();
|
||||
if now > next_autodelete {
|
||||
1
|
||||
} else {
|
||||
(next_autodelete - now)
|
||||
.try_into()
|
||||
.unwrap_or(u32::MAX)
|
||||
.saturating_add(1)
|
||||
}
|
||||
} else {
|
||||
0
|
||||
};
|
||||
emit_event!(context, Event::MsgDeleteTimeoutChanged { timer });
|
||||
}
|
||||
|
||||
/// Deletes messages which are expired according to "delete_device_after" setting.
|
||||
///
|
||||
/// Returns true if any message is deleted, so event can be emitted. If nothing
|
||||
/// has been deleted, returns false.
|
||||
pub fn delete_device_expired_messages(context: &Context) -> Result<bool, Error> {
|
||||
if let Some(delete_device_after) = context.get_config_delete_device_after() {
|
||||
let threshold_timestamp = time() - delete_device_after;
|
||||
pub(crate) fn delete_device_expired_messages(context: &Context) -> Result<bool, Error> {
|
||||
let now = time();
|
||||
|
||||
let self_chat_id = lookup_by_contact_id(context, DC_CONTACT_ID_SELF)
|
||||
.unwrap_or_default()
|
||||
.0;
|
||||
let device_chat_id = lookup_by_contact_id(context, DC_CONTACT_ID_DEVICE)
|
||||
.unwrap_or_default()
|
||||
.0;
|
||||
let threshold_timestamp = match context.get_config_delete_device_after() {
|
||||
None => 0,
|
||||
Some(delete_device_after) => now - delete_device_after,
|
||||
};
|
||||
|
||||
// Delete expired messages
|
||||
//
|
||||
// Only update the rows that have to be updated, to avoid emitting
|
||||
// unnecessary "chat modified" events.
|
||||
let rows_modified = context.sql.execute(
|
||||
"UPDATE msgs \
|
||||
let self_chat_id = lookup_by_contact_id(context, DC_CONTACT_ID_SELF)
|
||||
.unwrap_or_default()
|
||||
.0;
|
||||
let device_chat_id = lookup_by_contact_id(context, DC_CONTACT_ID_DEVICE)
|
||||
.unwrap_or_default()
|
||||
.0;
|
||||
|
||||
// Delete expired messages
|
||||
//
|
||||
// Only update the rows that have to be updated, to avoid emitting
|
||||
// unnecessary "chat modified" events.
|
||||
let rows_modified = context.sql.execute(
|
||||
"UPDATE msgs \
|
||||
SET txt = 'DELETED', chat_id = ? \
|
||||
WHERE timestamp < ? \
|
||||
WHERE \
|
||||
( \
|
||||
( \
|
||||
timestamp < ? \
|
||||
AND chat_id != ? \
|
||||
) \
|
||||
OR \
|
||||
( \
|
||||
autodelete_timestamp != 0 \
|
||||
AND autodelete_timestamp < ? \
|
||||
) \
|
||||
) \
|
||||
AND chat_id > ? \
|
||||
AND chat_id != ? \
|
||||
AND chat_id != ?",
|
||||
params![
|
||||
DC_CHAT_ID_TRASH,
|
||||
threshold_timestamp,
|
||||
DC_CHAT_ID_LAST_SPECIAL,
|
||||
self_chat_id,
|
||||
device_chat_id
|
||||
],
|
||||
)?;
|
||||
params![
|
||||
DC_CHAT_ID_TRASH,
|
||||
threshold_timestamp,
|
||||
self_chat_id,
|
||||
now,
|
||||
DC_CHAT_ID_LAST_SPECIAL,
|
||||
device_chat_id
|
||||
],
|
||||
)?;
|
||||
|
||||
Ok(rows_modified > 0)
|
||||
} else {
|
||||
Ok(false)
|
||||
let updated = rows_modified > 0;
|
||||
if updated {
|
||||
update_autodelete_timeout(context);
|
||||
}
|
||||
Ok(updated)
|
||||
}
|
||||
|
||||
pub fn get_chat_media(
|
||||
@@ -2567,6 +2635,63 @@ pub(crate) fn add_info_msg(context: &Context, chat_id: ChatId, text: impl AsRef<
|
||||
});
|
||||
}
|
||||
|
||||
/// Get autodelete timer value in seconds.
|
||||
pub fn get_autodelete_timer(context: &Context, chat_id: ChatId) -> u32 {
|
||||
context
|
||||
.sql
|
||||
.query_get_value(
|
||||
context,
|
||||
"SELECT autodelete_timer FROM chats WHERE id=?;",
|
||||
params![chat_id],
|
||||
)
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
/// Set autodelete timer value without sending a message.
|
||||
///
|
||||
/// Used when a message arrives indicating that someone else has
|
||||
/// changed the timer value for a chat.
|
||||
pub(crate) fn inner_set_autodelete_timer(
|
||||
context: &Context,
|
||||
chat_id: ChatId,
|
||||
timer: u32,
|
||||
) -> Result<(), Error> {
|
||||
ensure!(!chat_id.is_special(), "Invalid chat ID");
|
||||
|
||||
context.sql.execute(
|
||||
"UPDATE chats
|
||||
SET autodelete_timer=?
|
||||
WHERE id=?;",
|
||||
params![timer, chat_id],
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Set autodelete timer value in seconds.
|
||||
///
|
||||
/// If timer value is 0, disable autodelete timer.
|
||||
pub fn set_autodelete_timer(context: &Context, chat_id: ChatId, timer: u32) -> Result<(), Error> {
|
||||
if timer == get_autodelete_timer(context, chat_id) {
|
||||
return Ok(());
|
||||
}
|
||||
inner_set_autodelete_timer(context, chat_id, timer)?;
|
||||
let mut msg = Message::new(Viewtype::Text);
|
||||
msg.text = Some(context.stock_system_msg(
|
||||
StockMessage::MsgAutodeleteTimerChanged,
|
||||
timer.to_string(),
|
||||
"",
|
||||
0,
|
||||
));
|
||||
msg.param.set_cmd(SystemMessage::AutodeleteTimerChanged);
|
||||
if let Err(err) = send_msg(context, chat_id, &mut msg) {
|
||||
warn!(
|
||||
context,
|
||||
"Failed to send a message about autodelete timer change: {:?}", err
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@@ -2597,7 +2722,8 @@ mod tests {
|
||||
"color": 15895624,
|
||||
"profile_image": "",
|
||||
"draft": "",
|
||||
"is_muted": false
|
||||
"is_muted": false,
|
||||
"autodelete_timer": 0
|
||||
}
|
||||
"#;
|
||||
|
||||
|
||||
@@ -221,6 +221,19 @@ pub fn dc_receive_imf(
|
||||
}
|
||||
}
|
||||
|
||||
// if we delete we don't need to try moving messages
|
||||
if needs_delete_job && !created_db_entries.is_empty() {
|
||||
job_add(
|
||||
context,
|
||||
Action::DeleteMsgOnImap,
|
||||
created_db_entries[0].1.to_u32() as i32,
|
||||
Params::new(),
|
||||
0,
|
||||
);
|
||||
} else {
|
||||
context.do_heuristics_moves(server_folder.as_ref(), insert_msg_id);
|
||||
}
|
||||
|
||||
info!(
|
||||
context,
|
||||
"received message {} has Message-Id: {}", server_uid, rfc724_mid
|
||||
@@ -578,6 +591,48 @@ fn add_parts(
|
||||
*chat_id = ChatId::new(DC_CHAT_ID_TRASH);
|
||||
}
|
||||
}
|
||||
|
||||
// Extract autodelete timer from the message.
|
||||
let timer = if let Some(value) = mime_parser.get(HeaderDef::AutodeleteTimer) {
|
||||
match value.parse::<u32>() {
|
||||
Ok(timer) => timer,
|
||||
Err(err) => {
|
||||
warn!(
|
||||
context,
|
||||
"can't parse autodelete timer \"{}\": {}", value, err
|
||||
);
|
||||
0
|
||||
}
|
||||
}
|
||||
} else {
|
||||
0
|
||||
};
|
||||
|
||||
// Apply autodelete timer changes to the chat.
|
||||
if chat::get_autodelete_timer(context, *chat_id) != timer {
|
||||
match chat::inner_set_autodelete_timer(context, *chat_id, timer) {
|
||||
Ok(()) => {
|
||||
let stock_str = context.stock_system_msg(
|
||||
StockMessage::MsgAutodeleteTimerChanged,
|
||||
timer.to_string(),
|
||||
"",
|
||||
from_id,
|
||||
);
|
||||
chat::add_info_msg(context, *chat_id, stock_str);
|
||||
context.call_cb(Event::ChatAutodeleteTimerModified {
|
||||
chat_id: *chat_id,
|
||||
timer,
|
||||
});
|
||||
}
|
||||
Err(err) => {
|
||||
warn!(
|
||||
context,
|
||||
"failed to modify timer for chat {}: {}", chat_id, err
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// correct message_timestamp, it should not be used before,
|
||||
// however, we cannot do this earlier as we need from_id to be set
|
||||
calc_timestamps(
|
||||
@@ -617,8 +672,8 @@ fn add_parts(
|
||||
"INSERT INTO msgs \
|
||||
(rfc724_mid, server_folder, server_uid, chat_id, from_id, to_id, timestamp, \
|
||||
timestamp_sent, timestamp_rcvd, type, state, msgrmsg, txt, txt_raw, param, \
|
||||
bytes, hidden, mime_headers, mime_in_reply_to, mime_references) \
|
||||
VALUES (?,?,?,?,?,?, ?,?,?,?,?,?, ?,?,?,?,?,?, ?,?);",
|
||||
bytes, hidden, mime_headers, mime_in_reply_to, mime_references, autodelete_timer) \
|
||||
VALUES (?,?,?,?,?,?, ?,?,?,?,?,?, ?,?,?,?,?,?, ?,?,?);",
|
||||
|mut stmt, conn| {
|
||||
let subject = mime_parser.get_subject().unwrap_or_default();
|
||||
|
||||
@@ -671,6 +726,7 @@ fn add_parts(
|
||||
},
|
||||
mime_in_reply_to,
|
||||
mime_references,
|
||||
timer
|
||||
])?;
|
||||
|
||||
txt_raw = None;
|
||||
|
||||
@@ -132,13 +132,28 @@ pub enum Event {
|
||||
#[strum(props(id = "2015"))]
|
||||
MsgRead { chat_id: ChatId, msg_id: MsgId },
|
||||
|
||||
/// Time left until the next message deletion has changed.
|
||||
///
|
||||
/// The only parameter is the number of seconds left until next message
|
||||
/// deletion. It is rounded up. If the timer is 0, it means there are no
|
||||
/// messages to be deleted in the future.
|
||||
#[strum(props(id = "2016"))]
|
||||
MsgDeleteTimeoutChanged { timer: u32 },
|
||||
|
||||
/// Chat changed. The name or the image of a chat group was changed or members were added or removed.
|
||||
/// Or the verify state of a chat has changed.
|
||||
/// See dc_set_chat_name(), dc_set_chat_profile_image(), dc_add_contact_to_chat()
|
||||
/// and dc_remove_contact_from_chat().
|
||||
///
|
||||
/// This event does not include autodelete timer modification, which
|
||||
/// is a separate event.
|
||||
#[strum(props(id = "2020"))]
|
||||
ChatModified(ChatId),
|
||||
|
||||
/// Chat autodelete timer changed.
|
||||
#[strum(props(id = "2021"))]
|
||||
ChatAutodeleteTimerModified { chat_id: ChatId, timer: u32 },
|
||||
|
||||
/// Contact(s) created, renamed, blocked or deleted.
|
||||
///
|
||||
/// @param data1 (int) If set, this is the contact_id of an added contact that should be selected.
|
||||
|
||||
@@ -41,6 +41,7 @@ pub enum HeaderDef {
|
||||
SecureJoinFingerprint,
|
||||
SecureJoinInvitenumber,
|
||||
SecureJoinAuth,
|
||||
AutodeleteTimer,
|
||||
_TestHeader,
|
||||
}
|
||||
|
||||
|
||||
28
src/job.rs
28
src/job.rs
@@ -994,19 +994,23 @@ pub fn job_send_msg(context: &Context, msg_id: MsgId) -> Result<()> {
|
||||
}
|
||||
|
||||
fn load_imap_deletion_msgid(context: &Context) -> sql::Result<Option<MsgId>> {
|
||||
if let Some(delete_server_after) = context.get_config_delete_server_after() {
|
||||
let threshold_timestamp = time() - delete_server_after;
|
||||
let now = time();
|
||||
|
||||
context.sql.query_row_optional(
|
||||
"SELECT id FROM msgs \
|
||||
WHERE timestamp < ? \
|
||||
AND server_uid != 0",
|
||||
params![threshold_timestamp],
|
||||
|row| row.get::<_, MsgId>(0),
|
||||
)
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
let threshold_timestamp = match context.get_config_delete_server_after() {
|
||||
None => 0,
|
||||
Some(delete_server_after) => now - delete_server_after,
|
||||
};
|
||||
|
||||
context.sql.query_row_optional(
|
||||
"SELECT id FROM msgs \
|
||||
WHERE ( \
|
||||
timestamp < ? \
|
||||
OR (autodelete_timestamp != 0 AND autodelete_timestamp < ?) \
|
||||
) \
|
||||
AND server_uid != 0",
|
||||
params![threshold_timestamp, now],
|
||||
|row| row.get::<_, MsgId>(0),
|
||||
)
|
||||
}
|
||||
|
||||
fn load_imap_deletion_job(context: &Context) -> sql::Result<Option<Job>> {
|
||||
|
||||
@@ -6,7 +6,7 @@ use deltachat_derive::{FromSql, ToSql};
|
||||
use lazy_static::lazy_static;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::chat::{self, Chat, ChatId};
|
||||
use crate::chat::{self, update_autodelete_timeout, Chat, ChatId};
|
||||
use crate::constants::*;
|
||||
use crate::contact::*;
|
||||
use crate::context::*;
|
||||
@@ -133,6 +133,36 @@ impl MsgId {
|
||||
)
|
||||
}
|
||||
|
||||
/// Returns autodelete timer value for the message.
|
||||
pub(crate) fn autodelete_timer(self, context: &Context) -> sql::Result<Option<i64>> {
|
||||
let res = match context.sql.query_get_value_result(
|
||||
"SELECT autodelete_timer FROM msgs WHERE id=?",
|
||||
params![self],
|
||||
)? {
|
||||
None | Some(0) => None,
|
||||
Some(timer) => Some(timer),
|
||||
};
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
/// Starts autodelete timer for the message if it is not started yet.
|
||||
pub(crate) fn start_autodelete_timer(self, context: &Context) -> sql::Result<()> {
|
||||
if let Some(autodelete_timer) = self.autodelete_timer(context)? {
|
||||
let autodelete_timestamp = time() + autodelete_timer;
|
||||
|
||||
sql::execute(
|
||||
context,
|
||||
&context.sql,
|
||||
"UPDATE msgs SET autodelete_timestamp = ? \
|
||||
WHERE (autodelete_timestamp == 0 OR autodelete_timestamp > ?) \
|
||||
AND id = ?",
|
||||
params![autodelete_timestamp, autodelete_timestamp, self],
|
||||
)?;
|
||||
update_autodelete_timeout(context);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Bad evil escape hatch.
|
||||
///
|
||||
/// Avoid using this, eventually types should be cleaned up enough
|
||||
@@ -247,6 +277,8 @@ pub struct Message {
|
||||
pub(crate) timestamp_sort: i64,
|
||||
pub(crate) timestamp_sent: i64,
|
||||
pub(crate) timestamp_rcvd: i64,
|
||||
pub(crate) autodelete_timer: i64,
|
||||
pub(crate) autodelete_timestamp: i64,
|
||||
pub(crate) text: Option<String>,
|
||||
pub(crate) rfc724_mid: String,
|
||||
pub(crate) in_reply_to: Option<String>,
|
||||
@@ -288,6 +320,8 @@ impl Message {
|
||||
" m.timestamp AS timestamp,",
|
||||
" m.timestamp_sent AS timestamp_sent,",
|
||||
" m.timestamp_rcvd AS timestamp_rcvd,",
|
||||
" m.autodelete_timer AS autodelete_timer,",
|
||||
" m.autodelete_timestamp AS autodelete_timestamp,",
|
||||
" m.type AS type,",
|
||||
" m.state AS state,",
|
||||
" m.msgrmsg AS msgrmsg,",
|
||||
@@ -315,6 +349,8 @@ impl Message {
|
||||
msg.timestamp_sort = row.get("timestamp")?;
|
||||
msg.timestamp_sent = row.get("timestamp_sent")?;
|
||||
msg.timestamp_rcvd = row.get("timestamp_rcvd")?;
|
||||
msg.autodelete_timer = row.get("autodelete_timer")?;
|
||||
msg.autodelete_timestamp = row.get("autodelete_timestamp")?;
|
||||
msg.viewtype = row.get("type")?;
|
||||
msg.state = row.get("state")?;
|
||||
msg.is_dc_message = row.get("msgrmsg")?;
|
||||
@@ -865,6 +901,17 @@ pub fn get_msg_info(context: &Context, msg_id: MsgId) -> String {
|
||||
ret += "\n";
|
||||
}
|
||||
|
||||
if msg.autodelete_timer != 0 {
|
||||
ret += &format!("Autodelete timer: {}\n", msg.autodelete_timer);
|
||||
}
|
||||
|
||||
if msg.autodelete_timestamp != 0 {
|
||||
ret += &format!(
|
||||
"Expires: {}\n",
|
||||
dc_timestamp_to_str(msg.autodelete_timestamp)
|
||||
);
|
||||
}
|
||||
|
||||
if msg.from_id == DC_CONTACT_ID_INFO || msg.to_id == DC_CONTACT_ID_INFO {
|
||||
// device-internal message, no further details needed
|
||||
return ret;
|
||||
@@ -1060,6 +1107,14 @@ pub fn markseen_msgs(context: &Context, msg_ids: &[MsgId]) -> bool {
|
||||
let msgs = msgs.unwrap_or_default();
|
||||
|
||||
for (id, curr_state, curr_blocked) in msgs.into_iter() {
|
||||
if let Err(err) = id.start_autodelete_timer(context) {
|
||||
warn!(
|
||||
context,
|
||||
"Failed to start autodelete timer for message {}: {}", id, err
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
if curr_blocked == Blocked::Not {
|
||||
if curr_state == MessageState::InFresh || curr_state == MessageState::InNoticed {
|
||||
update_msg_state(context, *id, MessageState::InSeen);
|
||||
|
||||
@@ -2,7 +2,7 @@ use chrono::TimeZone;
|
||||
use lettre_email::{mime, Address, Header, MimeMultipartType, PartBuilder};
|
||||
|
||||
use crate::blob::BlobObject;
|
||||
use crate::chat::{self, Chat};
|
||||
use crate::chat::{self, get_autodelete_timer, Chat};
|
||||
use crate::config::Config;
|
||||
use crate::constants::*;
|
||||
use crate::contact::*;
|
||||
@@ -453,6 +453,14 @@ impl<'a, 'b> MimeFactory<'a, 'b> {
|
||||
Loaded::MDN { .. } => dc_create_outgoing_rfc724_mid(None, &self.from_addr),
|
||||
};
|
||||
|
||||
let autodelete_timer = get_autodelete_timer(self.context, self.msg.chat_id);
|
||||
if autodelete_timer > 0 {
|
||||
protected_headers.push(Header::new(
|
||||
"Autodelete-Timer".to_string(),
|
||||
autodelete_timer.to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
// we could also store the message-id in the protected headers
|
||||
// which would probably help to survive providers like
|
||||
// Outlook.com or hotmail which mangle the Message-ID.
|
||||
@@ -702,6 +710,12 @@ impl<'a, 'b> MimeFactory<'a, 'b> {
|
||||
"location-streaming-enabled".into(),
|
||||
));
|
||||
}
|
||||
SystemMessage::AutodeleteTimerChanged => {
|
||||
protected_headers.push(Header::new(
|
||||
"Chat-Content".to_string(),
|
||||
"autodelete-timer-changed".to_string(),
|
||||
));
|
||||
}
|
||||
SystemMessage::AutocryptSetupMessage => {
|
||||
unprotected_headers
|
||||
.push(Header::new("Autocrypt-Setup-Message".into(), "v1".into()));
|
||||
|
||||
@@ -71,6 +71,9 @@ pub enum SystemMessage {
|
||||
SecurejoinMessage = 7,
|
||||
LocationStreamingEnabled = 8,
|
||||
LocationOnly = 9,
|
||||
|
||||
/// Chat autodelete timer is changed.
|
||||
AutodeleteTimerChanged = 10,
|
||||
}
|
||||
|
||||
impl Default for SystemMessage {
|
||||
@@ -206,6 +209,8 @@ impl MimeMessage {
|
||||
} else if let Some(value) = self.get(HeaderDef::ChatContent) {
|
||||
if value == "location-streaming-enabled" {
|
||||
self.is_system_message = SystemMessage::LocationStreamingEnabled;
|
||||
} else if value == "autodelete-timer-changed" {
|
||||
self.is_system_message = SystemMessage::AutodeleteTimerChanged;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
@@ -813,9 +818,15 @@ impl MimeMessage {
|
||||
.collect()
|
||||
});
|
||||
|
||||
let autodelete_timer = report_fields
|
||||
.get_header_value(HeaderDef::AutodeleteTimer)
|
||||
.and_then(|v| v.parse::<u32>().ok())
|
||||
.unwrap_or_default();
|
||||
|
||||
return Ok(Some(Report {
|
||||
original_message_id,
|
||||
additional_message_ids,
|
||||
autodelete_timer,
|
||||
}));
|
||||
}
|
||||
}
|
||||
@@ -899,6 +910,11 @@ pub(crate) struct Report {
|
||||
original_message_id: String,
|
||||
/// Additional-Message-IDs
|
||||
additional_message_ids: Vec<String>,
|
||||
|
||||
/// MDNs should contain the same autodelete timer value as used in
|
||||
/// the message they reference. It is used to determine autodelete
|
||||
/// timer support.
|
||||
autodelete_timer: u32,
|
||||
}
|
||||
|
||||
pub(crate) fn parse_message_ids(ids: &str) -> Result<Vec<String>> {
|
||||
|
||||
27
src/sql.rs
27
src/sql.rs
@@ -902,6 +902,33 @@ fn open(
|
||||
sql.execute("UPDATE chats SET grpid='' WHERE type=100", NO_PARAMS)?;
|
||||
sql.set_raw_config_int(context, "dbversion", 63)?;
|
||||
}
|
||||
if dbversion < 64 {
|
||||
info!(context, "[migration] v64");
|
||||
sql.execute(
|
||||
"ALTER TABLE chats ADD COLUMN autodelete_timer INTEGER DEFAULT 0;",
|
||||
NO_PARAMS,
|
||||
)?;
|
||||
sql.set_raw_config_int(context, "dbversion", 64)?;
|
||||
}
|
||||
if dbversion < 65 {
|
||||
info!(context, "[migration] v65");
|
||||
// Timer value in seconds. For incoming messages this
|
||||
// timer starts when message is read, so we want to have
|
||||
// the value stored here until the timer starts.
|
||||
sql.execute(
|
||||
"ALTER TABLE msgs ADD COLUMN autodelete_timer INTEGER DEFAULT 0;",
|
||||
NO_PARAMS,
|
||||
)?;
|
||||
// Timestamp indicating when the message should be
|
||||
// deleted. It is convenient to store it here because UI
|
||||
// needs this value to display how much time is left until
|
||||
// the message is deleted.
|
||||
sql.execute(
|
||||
"ALTER TABLE msgs ADD COLUMN autodelete_timestamp INTEGER DEFAULT 0;",
|
||||
NO_PARAMS,
|
||||
)?;
|
||||
sql.set_raw_config_int(context, "dbversion", 65)?;
|
||||
}
|
||||
|
||||
// (2) updates that require high-level objects
|
||||
// (the structure is complete now and all objects are usable)
|
||||
|
||||
@@ -179,6 +179,9 @@ pub enum StockMessage {
|
||||
|
||||
#[strum(props(fallback = "Unknown Sender for this chat. See 'info' for more details."))]
|
||||
UnknownSenderForChat = 72,
|
||||
|
||||
#[strum(props(fallback = "Autodelete timer changed to %1$s."))]
|
||||
MsgAutodeleteTimerChanged = 73,
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
Reference in New Issue
Block a user