Compare commits

...

26 Commits

Author SHA1 Message Date
Alexander Krotov
fb325be888 update_autodelete_timeout: ignore trashed messages
They are removed in prune_tombstones() which only happens during housekeeping.
2020-05-24 02:52:32 +03:00
Alexander Krotov
f3137d87c5 Add DC_EVENT_MSG_DELETE_TIMEOUT_CHANGED
This event tells the client when the next message will be deleted. When
this event is received, the client should start a timer and update
current chat or chatlist when timeout expires.
2020-05-24 02:52:32 +03:00
Alexander Krotov
9d033e2ea1 Start autodelete timer when message is displayed 2020-05-23 21:09:33 +03:00
Alexander Krotov
cdc15ce980 Do not store NULL values in msgs.autodelete_* columns 2020-05-23 21:09:33 +03:00
Alexander Krotov
f017ffa0b3 Cleanup test_autodelete_timer()
Better comments and removed unnecessary .consume_events()
2020-05-23 21:09:33 +03:00
Alexander Krotov
28c79516da Test that autodelete timer is set for received messages 2020-05-23 21:09:33 +03:00
Alexander Krotov
af1be65e8b Set autodelete timer for received messages 2020-05-23 21:09:33 +03:00
Alexander Krotov
93ffcf530d Add autodelete timer and timestamp to message info 2020-05-23 21:09:33 +03:00
Alexander Krotov
028187eeaf Generate IMAP deletion jobs for ephemeral messages 2020-05-23 21:09:33 +03:00
Alexander Krotov
032b387c95 Process timer changes before calc_timestamps()
This way system message about timer change gets lower timestamp than the
message itself, and it is clear that new timer is applied to the message
that just arrived.
2020-05-23 21:09:33 +03:00
Alexander Krotov
5523e7ba19 Delete autodelete-expired messages locally 2020-05-23 21:09:33 +03:00
Alexander Krotov
b54c2a3196 Do not set autodelete timer on special chat IDs 2020-05-23 21:09:33 +03:00
Alexander Krotov
42d8502531 Parse Autodelete-Timer header in MDNs
It is not sent there yet.
2020-05-23 21:09:33 +03:00
Alexander Krotov
6002f54ce1 Set autodelete timer columns for outgoing messages 2020-05-23 21:09:33 +03:00
Alexander Krotov
a830d499eb Add autodelete timer columns to message table 2020-05-23 21:09:32 +03:00
Alexander Krotov
49e821d029 Apply autodelete timer changes to the chat before adding messages
Timer is started only when the message is read, but we want to be completely sure that correct value is used.

This commit also add some comments.
2020-05-23 21:09:32 +03:00
Alexander Krotov
010cf7140f Do not send messages in autodelete timer tests
System messages are sent automatically now, so it is not needed.
2020-05-23 21:09:32 +03:00
Alexander Krotov
e1773edf7f Send system message to chat when user changes autodelete timer 2020-05-23 21:09:32 +03:00
Alexander Krotov
b4337685cf Add info message when autodelete timer is changed 2020-05-23 21:09:32 +03:00
Alexander Krotov
997c5ad68a Set timer to 0 if no Autodelete-Timer header is present 2020-05-23 21:09:32 +03:00
Alexander Krotov
67b486bfbd Transmit autodelete timer over the network in test_autodelete_timer 2020-05-23 21:09:32 +03:00
Alexander Krotov
f5ee9c530d Add Event::ChatAutodeleteTimerModified 2020-05-23 21:09:32 +03:00
Alexander Krotov
0189644f08 Add python test for autodelete timer API 2020-05-23 21:09:32 +03:00
Alexander Krotov
0ca3e3100e Add {get,set}_autodelete_timer to python API 2020-05-23 21:09:32 +03:00
Alexander Krotov
d4c4070b3f Add C interface for autodelete timer settings 2020-05-23 21:09:32 +03:00
Alexander Krotov
51133ce0d6 Add autodelete timer to the database 2020-05-23 21:09:32 +03:00
15 changed files with 518 additions and 47 deletions

View File

@@ -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.

View File

@@ -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,

View File

@@ -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.

View File

@@ -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

View File

@@ -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):

View File

@@ -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
}
"#;

View File

@@ -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;

View File

@@ -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.

View File

@@ -41,6 +41,7 @@ pub enum HeaderDef {
SecureJoinFingerprint,
SecureJoinInvitenumber,
SecureJoinAuth,
AutodeleteTimer,
_TestHeader,
}

View File

@@ -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>> {

View File

@@ -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);

View File

@@ -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()));

View File

@@ -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>> {

View File

@@ -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)

View File

@@ -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,
}
/*