From d11d3ab08bb743231b039b0404c8910999107bf2 Mon Sep 17 00:00:00 2001 From: Simon Laux Date: Tue, 7 Jan 2020 02:25:27 +0100 Subject: [PATCH 01/19] fix typo --- src/chat.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/chat.rs b/src/chat.rs index dc485afc9..43a6dd865 100644 --- a/src/chat.rs +++ b/src/chat.rs @@ -2398,7 +2398,7 @@ mod tests { let chat = Chat::load_from_db(&t.ctx, chat_id).unwrap(); let info = chat.get_info(&t.ctx).unwrap(); - // Ensure we can serialise this. + // Ensure we can serialize this. println!("{}", serde_json::to_string_pretty(&info).unwrap()); let expected = r#" @@ -2417,7 +2417,7 @@ mod tests { } "#; - // Ensure we can deserialise this. + // Ensure we can deserialize this. let loaded: ChatInfo = serde_json::from_str(expected).unwrap(); assert_eq!(info, loaded); } From b37e83caaba48e7fc674e82f8ac3bcd97ac7a731 Mon Sep 17 00:00:00 2001 From: Simon Laux Date: Tue, 7 Jan 2020 03:54:31 +0100 Subject: [PATCH 02/19] add possibility to mute chat in core --- src/chat.rs | 109 +++++++++++++++++++++++++++++++++++++++++++++++++++- src/sql.rs | 8 ++++ 2 files changed, 115 insertions(+), 2 deletions(-) diff --git a/src/chat.rs b/src/chat.rs index 43a6dd865..d9c6c4a56 100644 --- a/src/chat.rs +++ b/src/chat.rs @@ -422,6 +422,7 @@ pub struct Chat { blocked: Blocked, pub param: Params, is_sending_locations: bool, + pub mute_duration: MuteDuration, } impl Chat { @@ -429,7 +430,7 @@ impl Chat { pub fn load_from_db(context: &Context, chat_id: ChatId) -> Result { let res = context.sql.query_row( "SELECT c.type, c.name, c.grpid, c.param, c.archived, - c.blocked, c.locations_send_until + c.blocked, c.locations_send_until, c.muted_until FROM chats c WHERE c.id=?;", params![chat_id], @@ -443,6 +444,7 @@ impl Chat { archived: row.get(4)?, blocked: row.get::<_, Option<_>>(5)?.unwrap_or_default(), is_sending_locations: row.get(6)?, + mute_duration: MuteDuration::deserialize(row.get(7)?), }; Ok(c) }, @@ -658,6 +660,7 @@ impl Chat { profile_image: self.get_profile_image(context).unwrap_or_else(PathBuf::new), subtitle: self.get_subtitle(context), draft, + is_muted: self.is_muted(), }) } @@ -684,6 +687,14 @@ impl Chat { self.is_sending_locations } + pub fn is_muted(&self) -> bool { + match self.mute_duration { + MuteDuration::NotMuted => false, + MuteDuration::Forever => true, + MuteDuration::MutedUntilTimestamp(timestamp) => timestamp > time(), + } + } + fn prepare_msg_raw( &mut self, context: &Context, @@ -968,6 +979,11 @@ pub struct ChatInfo { /// which contain non-text parts. Perhaps it should be a /// simple `has_draft` bool instead. pub draft: String, + + /// Wether the chat is muted + /// + /// The exact time its muted can be found out via the `chat.mute_duration` property + pub is_muted:bool, // ToDo: // - [ ] deaddrop, // - [ ] summary, @@ -1901,6 +1917,56 @@ pub fn shall_attach_selfavatar(context: &Context, chat_id: ChatId) -> Result i64 { + match &self { + MuteDuration::NotMuted => 0, + MuteDuration::Forever => 1, + MuteDuration::MutedUntilTimestamp(timestamp) => *timestamp as i64, // TODO make this pretier? + } + } + + fn deserialize (value: i64) -> MuteDuration { + match value { + 0 => MuteDuration::NotMuted, + 1 => MuteDuration::Forever, + _ => MuteDuration::MutedUntilTimestamp(value), + } + } +} + +pub fn set_muted ( + context: &Context, + chat_id: ChatId, + duration: MuteDuration +) -> Result<(), Error>{ + let mut success = false; + ensure!(!chat_id.is_special(), "Invalid chat ID"); + if real_group_exists(context, chat_id) && sql::execute( + context, + &context.sql, + "UPDATE chats SET muted_until=? WHERE id=?;", + params![duration.serialize(), chat_id], + ).is_ok() { + context.call_cb(Event::ChatModified(chat_id)); + success = true; + } + + if !success { + bail!("Failed to set name"); + } + + Ok(()) +} + pub fn remove_contact_from_chat( context: &Context, chat_id: ChatId, @@ -2386,6 +2452,7 @@ pub fn add_info_msg(context: &Context, chat_id: ChatId, text: impl AsRef) { #[cfg(test)] mod tests { use super::*; + use std::time::{Duration, SystemTime, UNIX_EPOCH}; use crate::contact::Contact; use crate::test_utils::*; @@ -2413,7 +2480,8 @@ mod tests { "color": 15895624, "profile_image": "", "subtitle": "bob@example.com", - "draft": "" + "draft": "", + "is_muted": false } "#; @@ -2777,4 +2845,41 @@ mod tests { assert!(chat_id.set_selfavatar_timestamp(&t.ctx, time()).is_ok()); assert!(!shall_attach_selfavatar(&t.ctx, chat_id).unwrap()); } + + #[test] + fn test_set_mute_duration() { + let t = dummy_context(); + let chat_id = create_group_chat(&t.ctx, VerifiedStatus::Unverified, "foo").unwrap(); + // Initial + assert_eq!( + Chat::load_from_db(&t.ctx, chat_id).unwrap().is_muted(), + false + ); + // Forever + set_muted(&t.ctx, chat_id, MuteDuration::Forever).unwrap(); + assert_eq!( + Chat::load_from_db(&t.ctx, chat_id).unwrap().is_muted(), + true + ); + // unMute + set_muted(&t.ctx, chat_id, MuteDuration::NotMuted).unwrap(); + assert_eq!( + Chat::load_from_db(&t.ctx, chat_id).unwrap().is_muted(), + false + ); + // Timed in the future + let current_timestamp = SystemTime::now().duration_since(UNIX_EPOCH).unwrap() + Duration::from_secs(3600); + set_muted(&t.ctx, chat_id, MuteDuration::MutedUntilTimestamp(current_timestamp.as_secs() as i64)).unwrap(); + assert_eq!( + Chat::load_from_db(&t.ctx, chat_id).unwrap().is_muted(), + true + ); + // Time in the past + let past_timestamp = SystemTime::now().duration_since(UNIX_EPOCH).unwrap() - Duration::from_secs(3600); + set_muted(&t.ctx, chat_id, MuteDuration::MutedUntilTimestamp(past_timestamp.as_secs() as i64)).unwrap(); + assert_eq!( + Chat::load_from_db(&t.ctx, chat_id).unwrap().is_muted(), + false + ); + } } diff --git a/src/sql.rs b/src/sql.rs index e722d539c..181be75df 100644 --- a/src/sql.rs +++ b/src/sql.rs @@ -885,6 +885,14 @@ fn open( update_icons = true; sql.set_raw_config_int(context, "dbversion", 61)?; } + if dbversion < 62 { + info!(context, "[migration] v62"); + sql.execute( + "ALTER TABLE chats ADD COLUMN muted_until INTEGER DEFAULT 0;", + NO_PARAMS, + )?; + sql.set_raw_config_int(context, "dbversion", 62)?; + } // (2) updates that require high-level objects // (the structure is complete now and all objects are usable) From 1538684c6c02eac9e25d2f6b89dfd1c08c5c6aa1 Mon Sep 17 00:00:00 2001 From: Simon Laux Date: Tue, 7 Jan 2020 03:56:03 +0100 Subject: [PATCH 03/19] simplify test code --- src/chat.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/chat.rs b/src/chat.rs index d9c6c4a56..6d5005d5f 100644 --- a/src/chat.rs +++ b/src/chat.rs @@ -2868,15 +2868,13 @@ mod tests { false ); // Timed in the future - let current_timestamp = SystemTime::now().duration_since(UNIX_EPOCH).unwrap() + Duration::from_secs(3600); - set_muted(&t.ctx, chat_id, MuteDuration::MutedUntilTimestamp(current_timestamp.as_secs() as i64)).unwrap(); + set_muted(&t.ctx, chat_id, MuteDuration::MutedUntilTimestamp(time() + 3600)).unwrap(); assert_eq!( Chat::load_from_db(&t.ctx, chat_id).unwrap().is_muted(), true ); // Time in the past - let past_timestamp = SystemTime::now().duration_since(UNIX_EPOCH).unwrap() - Duration::from_secs(3600); - set_muted(&t.ctx, chat_id, MuteDuration::MutedUntilTimestamp(past_timestamp.as_secs() as i64)).unwrap(); + set_muted(&t.ctx, chat_id, MuteDuration::MutedUntilTimestamp(time() - 3600)).unwrap(); assert_eq!( Chat::load_from_db(&t.ctx, chat_id).unwrap().is_muted(), false From e006d9b0330977b1a5a22ff2a2402b8735fe72cf Mon Sep 17 00:00:00 2001 From: Simon Laux Date: Tue, 7 Jan 2020 04:24:43 +0100 Subject: [PATCH 04/19] rustfmt --- src/chat.rs | 45 +++++++++++++++++++++++++++------------------ 1 file changed, 27 insertions(+), 18 deletions(-) diff --git a/src/chat.rs b/src/chat.rs index 6d5005d5f..fca2cb50f 100644 --- a/src/chat.rs +++ b/src/chat.rs @@ -981,9 +981,9 @@ pub struct ChatInfo { pub draft: String, /// Wether the chat is muted - /// + /// /// The exact time its muted can be found out via the `chat.mute_duration` property - pub is_muted:bool, + pub is_muted: bool, // ToDo: // - [ ] deaddrop, // - [ ] summary, @@ -1926,7 +1926,7 @@ pub enum MuteDuration { impl MuteDuration { // TODO use serde compatible functions? - fn serialize (&self) -> i64 { + fn serialize(&self) -> i64 { match &self { MuteDuration::NotMuted => 0, MuteDuration::Forever => 1, @@ -1934,7 +1934,7 @@ impl MuteDuration { } } - fn deserialize (value: i64) -> MuteDuration { + fn deserialize(value: i64) -> MuteDuration { match value { 0 => MuteDuration::NotMuted, 1 => MuteDuration::Forever, @@ -1943,21 +1943,20 @@ impl MuteDuration { } } -pub fn set_muted ( - context: &Context, - chat_id: ChatId, - duration: MuteDuration -) -> Result<(), Error>{ +pub fn set_muted(context: &Context, chat_id: ChatId, duration: MuteDuration) -> Result<(), Error> { let mut success = false; ensure!(!chat_id.is_special(), "Invalid chat ID"); - if real_group_exists(context, chat_id) && sql::execute( - context, - &context.sql, - "UPDATE chats SET muted_until=? WHERE id=?;", - params![duration.serialize(), chat_id], - ).is_ok() { + if real_group_exists(context, chat_id) + && sql::execute( + context, + &context.sql, + "UPDATE chats SET muted_until=? WHERE id=?;", + params![duration.serialize(), chat_id], + ) + .is_ok() + { context.call_cb(Event::ChatModified(chat_id)); - success = true; + success = true; } if !success { @@ -2868,13 +2867,23 @@ mod tests { false ); // Timed in the future - set_muted(&t.ctx, chat_id, MuteDuration::MutedUntilTimestamp(time() + 3600)).unwrap(); + set_muted( + &t.ctx, + chat_id, + MuteDuration::MutedUntilTimestamp(time() + 3600), + ) + .unwrap(); assert_eq!( Chat::load_from_db(&t.ctx, chat_id).unwrap().is_muted(), true ); // Time in the past - set_muted(&t.ctx, chat_id, MuteDuration::MutedUntilTimestamp(time() - 3600)).unwrap(); + set_muted( + &t.ctx, + chat_id, + MuteDuration::MutedUntilTimestamp(time() - 3600), + ) + .unwrap(); assert_eq!( Chat::load_from_db(&t.ctx, chat_id).unwrap().is_muted(), false From b9ba1a4f69431f4aefe506d07daef36bf363dce5 Mon Sep 17 00:00:00 2001 From: Simon Laux Date: Tue, 7 Jan 2020 04:26:15 +0100 Subject: [PATCH 05/19] implement ffi part --- deltachat-ffi/deltachat.h | 36 +++++++++++++++++++++++++++++++++++ deltachat-ffi/src/lib.rs | 40 +++++++++++++++++++++++++++++++++++++++ src/chat.rs | 5 ++--- 3 files changed, 78 insertions(+), 3 deletions(-) diff --git a/deltachat-ffi/deltachat.h b/deltachat-ffi/deltachat.h index ff9128fd3..93db76c14 100644 --- a/deltachat-ffi/deltachat.h +++ b/deltachat-ffi/deltachat.h @@ -1577,6 +1577,22 @@ int dc_set_chat_name (dc_context_t* context, uint32_t ch int dc_set_chat_profile_image (dc_context_t* context, uint32_t chat_id, const char* image); + +/** + * Set mute duration of a chat. + * + * This value can be checked by the ui upon recieving a new message to decide whether it should trigger an notification. + * + * Sends out #DC_EVENT_CHAT_MODIFIED. + * + * @memberof dc_context_t + * @param chat_id The chat ID to set the mute duration. + * @param duration The duration (0 for no mute, 1 for forever mute, >1 unix timestamp it until it should be unmuted again) + * @param context The context as created by dc_context_new(). + * @return 1=success, 0=error + */ +int dc_set_chat_muted (dc_context_t* context, uint32_t chat_id, int64_t duration); + // handle messages /** @@ -2919,6 +2935,26 @@ int dc_chat_is_verified (const dc_chat_t* chat); int dc_chat_is_sending_locations (const dc_chat_t* chat); +/** + * Check wether the chat is currently muted + * + * @memberof dc_chat_t + * @param chat The chat object. + * @return 1=muted, 0=not muted + */ +int dc_chat_is_muted (const dc_chat_t* chat); + + +/** + * Get the exact state of the mute of a chat + * + * @memberof dc_chat_t + * @param chat The chat object. + * @return 0=not muted, 1=muted, (x>1)=unix timestamp until mute is lifted + */ +int64_t dc_chat_get_mute_duration (const dc_chat_t* chat); + + /** * @class dc_msg_t * diff --git a/deltachat-ffi/src/lib.rs b/deltachat-ffi/src/lib.rs index 383e4c242..4be954a9e 100644 --- a/deltachat-ffi/src/lib.rs +++ b/deltachat-ffi/src/lib.rs @@ -1407,6 +1407,26 @@ pub unsafe extern "C" fn dc_set_chat_profile_image( .unwrap_or(0) } +#[no_mangle] +pub unsafe extern "C" fn dc_set_chat_muted( + context: *mut dc_context_t, + chat_id: u32, + duration: i64, +) -> 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_muted()"); + return 0; + } + let ffi_context = &*context; + ffi_context + .with_inner(|ctx| { + chat::set_muted(ctx, chat_id, chat::MuteDuration::deserialize(duration)) + .map(|_| 1) + .unwrap_or_log_default(ctx, "Failed to set mute duration") + }) + .unwrap_or(0) +} + #[no_mangle] pub unsafe extern "C" fn dc_get_msg_info( context: *mut dc_context_t, @@ -2481,6 +2501,26 @@ pub unsafe extern "C" fn dc_chat_is_sending_locations(chat: *mut dc_chat_t) -> l ffi_chat.chat.is_sending_locations() as libc::c_int } +#[no_mangle] +pub unsafe extern "C" fn dc_chat_is_muted(chat: *mut dc_chat_t) -> libc::c_int { + if chat.is_null() { + eprintln!("ignoring careless call to dc_chat_is_muted()"); + return 0; + } + let ffi_chat = &*chat; + ffi_chat.chat.is_muted() as libc::c_int +} + +#[no_mangle] +pub unsafe extern "C" fn dc_chat_get_mute_duration(chat: *mut dc_chat_t) -> i64 { + if chat.is_null() { + eprintln!("ignoring careless call to dc_chat_is_muted()"); + return 0; + } + let ffi_chat = &*chat; + ffi_chat.chat.mute_duration.serialize() as i64 +} + #[no_mangle] pub unsafe extern "C" fn dc_chat_get_info_json( context: *mut dc_context_t, diff --git a/src/chat.rs b/src/chat.rs index fca2cb50f..82796d55d 100644 --- a/src/chat.rs +++ b/src/chat.rs @@ -1926,7 +1926,7 @@ pub enum MuteDuration { impl MuteDuration { // TODO use serde compatible functions? - fn serialize(&self) -> i64 { + pub fn serialize(&self) -> i64 { match &self { MuteDuration::NotMuted => 0, MuteDuration::Forever => 1, @@ -1934,7 +1934,7 @@ impl MuteDuration { } } - fn deserialize(value: i64) -> MuteDuration { + pub fn deserialize(value: i64) -> MuteDuration { match value { 0 => MuteDuration::NotMuted, 1 => MuteDuration::Forever, @@ -2451,7 +2451,6 @@ pub fn add_info_msg(context: &Context, chat_id: ChatId, text: impl AsRef) { #[cfg(test)] mod tests { use super::*; - use std::time::{Duration, SystemTime, UNIX_EPOCH}; use crate::contact::Contact; use crate::test_utils::*; From 63be1ae5a9b697e25cc4300b705cf2e4b3ff0b89 Mon Sep 17 00:00:00 2001 From: Simon Laux Date: Wed, 8 Jan 2020 02:40:26 +0100 Subject: [PATCH 06/19] change muted forever to -1 --- deltachat-ffi/deltachat.h | 6 +++--- src/chat.rs | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/deltachat-ffi/deltachat.h b/deltachat-ffi/deltachat.h index 93db76c14..4e8b307d8 100644 --- a/deltachat-ffi/deltachat.h +++ b/deltachat-ffi/deltachat.h @@ -1587,7 +1587,7 @@ int dc_set_chat_profile_image (dc_context_t* context, uint32_t ch * * @memberof dc_context_t * @param chat_id The chat ID to set the mute duration. - * @param duration The duration (0 for no mute, 1 for forever mute, >1 unix timestamp it until it should be unmuted again) + * @param duration The duration (0 for no mute, -1 for forever mute, >0 unix timestamp it until it should be unmuted again) * @param context The context as created by dc_context_new(). * @return 1=success, 0=error */ @@ -2936,7 +2936,7 @@ int dc_chat_is_sending_locations (const dc_chat_t* chat); /** - * Check wether the chat is currently muted + * Check whether the chat is currently muted * * @memberof dc_chat_t * @param chat The chat object. @@ -2950,7 +2950,7 @@ int dc_chat_is_muted (const dc_chat_t* chat); * * @memberof dc_chat_t * @param chat The chat object. - * @return 0=not muted, 1=muted, (x>1)=unix timestamp until mute is lifted + * @return 0=not muted, -1=forever muted, (x>0)=unix timestamp until mute is lifted */ int64_t dc_chat_get_mute_duration (const dc_chat_t* chat); diff --git a/src/chat.rs b/src/chat.rs index 82796d55d..0d1c9533a 100644 --- a/src/chat.rs +++ b/src/chat.rs @@ -980,7 +980,7 @@ pub struct ChatInfo { /// simple `has_draft` bool instead. pub draft: String, - /// Wether the chat is muted + /// Whether the chat is muted /// /// The exact time its muted can be found out via the `chat.mute_duration` property pub is_muted: bool, @@ -1929,7 +1929,7 @@ impl MuteDuration { pub fn serialize(&self) -> i64 { match &self { MuteDuration::NotMuted => 0, - MuteDuration::Forever => 1, + MuteDuration::Forever => -1, MuteDuration::MutedUntilTimestamp(timestamp) => *timestamp as i64, // TODO make this pretier? } } @@ -1937,7 +1937,7 @@ impl MuteDuration { pub fn deserialize(value: i64) -> MuteDuration { match value { 0 => MuteDuration::NotMuted, - 1 => MuteDuration::Forever, + -1 => MuteDuration::Forever, _ => MuteDuration::MutedUntilTimestamp(value), } } From ef158504e78d6e1aac14008ed912d36dc51c10e9 Mon Sep 17 00:00:00 2001 From: Simon Laux Date: Wed, 8 Jan 2020 06:51:56 +0100 Subject: [PATCH 07/19] remove success variable (replace with else statement) --- src/chat.rs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/chat.rs b/src/chat.rs index 0d1c9533a..741f7627e 100644 --- a/src/chat.rs +++ b/src/chat.rs @@ -1944,7 +1944,6 @@ impl MuteDuration { } pub fn set_muted(context: &Context, chat_id: ChatId, duration: MuteDuration) -> Result<(), Error> { - let mut success = false; ensure!(!chat_id.is_special(), "Invalid chat ID"); if real_group_exists(context, chat_id) && sql::execute( @@ -1956,10 +1955,7 @@ pub fn set_muted(context: &Context, chat_id: ChatId, duration: MuteDuration) -> .is_ok() { context.call_cb(Event::ChatModified(chat_id)); - success = true; - } - - if !success { + } else { bail!("Failed to set name"); } From 07d698f8dc1dd1fb6d2ba8a5a04afbaef781b828 Mon Sep 17 00:00:00 2001 From: Simon Laux Date: Fri, 24 Jan 2020 01:49:50 +0100 Subject: [PATCH 08/19] add python tests --- python/src/deltachat/chat.py | 37 +++++++++++++++++++++++++++++++++++- python/tests/test_account.py | 12 ++++++++++++ 2 files changed, 48 insertions(+), 1 deletion(-) diff --git a/python/src/deltachat/chat.py b/python/src/deltachat/chat.py index 00787b795..525c358f0 100644 --- a/python/src/deltachat/chat.py +++ b/python/src/deltachat/chat.py @@ -5,6 +5,7 @@ import calendar import json from datetime import datetime import os +import time from .cutil import as_dc_charpointer, from_dc_charpointer, iter_array from .capi import lib, ffi from . import const @@ -58,6 +59,13 @@ class Chat(object): """ return self.id == const.DC_CHAT_ID_DEADDROP + def is_muted(self): + """ return true if this chat is muted. + + :returns: True if chat is muted, False otherwise. + """ + return lib.dc_chat_is_muted(self._dc_chat) + def is_promoted(self): """ return True if this chat is promoted, i.e. the member contacts are aware of their membership, @@ -84,11 +92,38 @@ class Chat(object): def set_name(self, name): """ set name of this chat. - :param: name as a unicode string. + :param name: as a unicode string. :returns: None """ name = as_dc_charpointer(name) return lib.dc_set_chat_name(self._dc_context, self.id, name) + + def mute(self, duration=None): + """ mutes the chat + + :param duration: Number of seconds to mute the chat for. None to mute until unmuted again. + :returns: + """ + if duration is None: + timestamp = -1 + else: + timestamp = int(time.time()) + duration + return bool(lib.dc_set_chat_muted(self._dc_context, self.id, timestamp)) + + def unmute(self): + """ unmutes the chat + + :returns: + """ + return bool(lib.dc_set_chat_muted(self._dc_context, self.id, 0)) + + def get_mute_duration(self): + """ mutes the chat + + :param duration: + :returns: Returns the number of seconds the chat is still muted for. (0 for not muted, -1 forever muted) + """ + return bool(lib.dc_chat_get_mute_duration(self.id)) def get_type(self): """ return type of this chat. diff --git a/python/tests/test_account.py b/python/tests/test_account.py index e8536b949..0ae067675 100644 --- a/python/tests/test_account.py +++ b/python/tests/test_account.py @@ -220,6 +220,18 @@ class TestOfflineChat: chat.remove_profile_image() assert chat.get_profile_image() is None + def test_mute(self, ac1): + chat = ac1.create_group_chat(name="title1") + assert not chat.is_muted() + assert chat.mute() + assert chat.is_muted() + assert chat.unmute() + assert not chat.is_muted() + assert chat.mute(50) + assert chat.is_muted() + assert chat.mute(-51) + assert not chat.is_muted() + def test_delete_and_send_fails(self, ac1, chat1): chat1.delete() ac1._evlogger.get_matching("DC_EVENT_MSGS_CHANGED") From 6d80b3675a92e903f7144beff090e8d7d3315c5e Mon Sep 17 00:00:00 2001 From: Simon Laux Date: Sat, 8 Feb 2020 12:45:06 +0100 Subject: [PATCH 09/19] dtransform mute chat to use relative durations instead of absolute timestamps --- deltachat-ffi/deltachat.h | 6 +++--- deltachat-ffi/src/lib.rs | 28 +++++++++++++++++++++++----- deltachat-ffi/src/tools.rs | 8 ++++++++ python/src/deltachat/chat.py | 10 +++++----- src/chat.rs | 8 +++++++- 5 files changed, 46 insertions(+), 14 deletions(-) create mode 100644 deltachat-ffi/src/tools.rs diff --git a/deltachat-ffi/deltachat.h b/deltachat-ffi/deltachat.h index 4e8b307d8..634eab881 100644 --- a/deltachat-ffi/deltachat.h +++ b/deltachat-ffi/deltachat.h @@ -1587,11 +1587,11 @@ int dc_set_chat_profile_image (dc_context_t* context, uint32_t ch * * @memberof dc_context_t * @param chat_id The chat ID to set the mute duration. - * @param duration The duration (0 for no mute, -1 for forever mute, >0 unix timestamp it until it should be unmuted again) + * @param duration The duration (0 for no mute, -1 for forever mute, everything else is is the relative mute duration from now in seconds) * @param context The context as created by dc_context_new(). * @return 1=success, 0=error */ -int dc_set_chat_muted (dc_context_t* context, uint32_t chat_id, int64_t duration); +int dc_chat_set_mute_duration (dc_context_t* context, uint32_t chat_id, int64_t duration); // handle messages @@ -2950,7 +2950,7 @@ int dc_chat_is_muted (const dc_chat_t* chat); * * @memberof dc_chat_t * @param chat The chat object. - * @return 0=not muted, -1=forever muted, (x>0)=unix timestamp until mute is lifted + * @return 0=not muted, -1=forever muted, (x>0)=remaining seconds until the mute is lifted */ int64_t dc_chat_get_mute_duration (const dc_chat_t* chat); diff --git a/deltachat-ffi/src/lib.rs b/deltachat-ffi/src/lib.rs index 4be954a9e..ae69b3a28 100644 --- a/deltachat-ffi/src/lib.rs +++ b/deltachat-ffi/src/lib.rs @@ -25,6 +25,7 @@ use libc::uintptr_t; use num_traits::{FromPrimitive, ToPrimitive}; use deltachat::chat::ChatId; +use deltachat::chat::MuteDuration; use deltachat::constants::DC_MSG_ID_LAST_SPECIAL; use deltachat::contact::Contact; use deltachat::context::Context; @@ -33,6 +34,9 @@ use deltachat::message::MsgId; use deltachat::stock::StockMessage; use deltachat::*; +mod tools; +use crate::tools::time; + mod dc_array; mod string; @@ -1408,19 +1412,26 @@ pub unsafe extern "C" fn dc_set_chat_profile_image( } #[no_mangle] -pub unsafe extern "C" fn dc_set_chat_muted( +pub unsafe extern "C" fn dc_chat_set_mute_duration( context: *mut dc_context_t, chat_id: u32, duration: i64, ) -> 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_muted()"); + eprintln!("ignoring careless call to dc_chat_set_mute_duration()"); return 0; } + + let muteDuration = match duration { + 0 => MuteDuration::NotMuted, + -1 => MuteDuration::Forever, + _ => MuteDuration::MutedUntilTimestamp(time() + duration), + }; + let ffi_context = &*context; ffi_context .with_inner(|ctx| { - chat::set_muted(ctx, chat_id, chat::MuteDuration::deserialize(duration)) + chat::set_muted(ctx, chat_id, muteDuration) .map(|_| 1) .unwrap_or_log_default(ctx, "Failed to set mute duration") }) @@ -2514,11 +2525,18 @@ pub unsafe extern "C" fn dc_chat_is_muted(chat: *mut dc_chat_t) -> libc::c_int { #[no_mangle] pub unsafe extern "C" fn dc_chat_get_mute_duration(chat: *mut dc_chat_t) -> i64 { if chat.is_null() { - eprintln!("ignoring careless call to dc_chat_is_muted()"); + eprintln!("ignoring careless call to dc_chat_get_mute_duration()"); return 0; } let ffi_chat = &*chat; - ffi_chat.chat.mute_duration.serialize() as i64 + if !ffi_chat.chat.is_muted() { + return 0; + } + match ffi_chat.chat.mute_duration { + MuteDuration::NotMuted => 0, + MuteDuration::Forever => -1, + MuteDuration::MutedUntilTimestamp(timestamp) => timestamp - time(), + } } #[no_mangle] diff --git a/deltachat-ffi/src/tools.rs b/deltachat-ffi/src/tools.rs new file mode 100644 index 000000000..e7f7e8bb7 --- /dev/null +++ b/deltachat-ffi/src/tools.rs @@ -0,0 +1,8 @@ +use std::time::SystemTime; + +pub(crate) fn time() -> i64 { + SystemTime::now() + .duration_since(SystemTime::UNIX_EPOCH) + .unwrap_or_default() + .as_secs() as i64 +} diff --git a/python/src/deltachat/chat.py b/python/src/deltachat/chat.py index 525c358f0..0b5ff3e40 100644 --- a/python/src/deltachat/chat.py +++ b/python/src/deltachat/chat.py @@ -105,20 +105,20 @@ class Chat(object): :returns: """ if duration is None: - timestamp = -1 + mute_duration = -1 else: - timestamp = int(time.time()) + duration - return bool(lib.dc_set_chat_muted(self._dc_context, self.id, timestamp)) + mute_duration = duration + return bool(lib.dc_chat_set_mute_duration(self._dc_context, self.id, mute_duration)) def unmute(self): """ unmutes the chat :returns: """ - return bool(lib.dc_set_chat_muted(self._dc_context, self.id, 0)) + return bool(lib.dc_chat_set_mute_duration(self._dc_context, self.id, 0)) def get_mute_duration(self): - """ mutes the chat + """ Returns the number of seconds until the mute of this chat is lifted. :param duration: :returns: Returns the number of seconds the chat is still muted for. (0 for not muted, -1 forever muted) diff --git a/src/chat.rs b/src/chat.rs index 741f7627e..a1abb68dc 100644 --- a/src/chat.rs +++ b/src/chat.rs @@ -1938,7 +1938,13 @@ impl MuteDuration { match value { 0 => MuteDuration::NotMuted, -1 => MuteDuration::Forever, - _ => MuteDuration::MutedUntilTimestamp(value), + _ => { + if value <= time() { + MuteDuration::NotMuted + } else { + MuteDuration::MutedUntilTimestamp(value) + } + } } } } From e04d28c88532af33bc8259bbd4c2fc9b2153b6cf Mon Sep 17 00:00:00 2001 From: Simon Laux Date: Sat, 8 Feb 2020 12:54:26 +0100 Subject: [PATCH 10/19] use from- and to-sql traits --- src/chat.rs | 47 ++++++++++++++++++++++++++++------------------- 1 file changed, 28 insertions(+), 19 deletions(-) diff --git a/src/chat.rs b/src/chat.rs index a1abb68dc..6cc751687 100644 --- a/src/chat.rs +++ b/src/chat.rs @@ -444,7 +444,7 @@ impl Chat { archived: row.get(4)?, blocked: row.get::<_, Option<_>>(5)?.unwrap_or_default(), is_sending_locations: row.get(6)?, - mute_duration: MuteDuration::deserialize(row.get(7)?), + mute_duration: row.get(7)?, }; Ok(c) }, @@ -1924,28 +1924,37 @@ pub enum MuteDuration { MutedUntilTimestamp(i64), } -impl MuteDuration { - // TODO use serde compatible functions? - pub fn serialize(&self) -> i64 { - match &self { +impl rusqlite::types::ToSql for MuteDuration { + fn to_sql(&self) -> rusqlite::Result { + let duration = match &self { MuteDuration::NotMuted => 0, MuteDuration::Forever => -1, - MuteDuration::MutedUntilTimestamp(timestamp) => *timestamp as i64, // TODO make this pretier? - } + MuteDuration::MutedUntilTimestamp(timestamp) => *timestamp as i64, + }; + let val = rusqlite::types::Value::Integer(duration as i64); + let out = rusqlite::types::ToSqlOutput::Owned(val); + Ok(out) } +} - pub fn deserialize(value: i64) -> MuteDuration { - match value { - 0 => MuteDuration::NotMuted, - -1 => MuteDuration::Forever, - _ => { - if value <= time() { - MuteDuration::NotMuted - } else { - MuteDuration::MutedUntilTimestamp(value) +impl rusqlite::types::FromSql for MuteDuration { + fn column_result(value: rusqlite::types::ValueRef) -> rusqlite::types::FromSqlResult { + // Would be nice if we could use match here, but alas. + i64::column_result(value).and_then(|val| { + Ok({ + match val { + 0 => MuteDuration::NotMuted, + -1 => MuteDuration::Forever, + _ => { + if val <= time() { + MuteDuration::NotMuted + } else { + MuteDuration::MutedUntilTimestamp(val) + } + } } - } - } + }) + }) } } @@ -1956,7 +1965,7 @@ pub fn set_muted(context: &Context, chat_id: ChatId, duration: MuteDuration) -> context, &context.sql, "UPDATE chats SET muted_until=? WHERE id=?;", - params![duration.serialize(), chat_id], + params![duration, chat_id], ) .is_ok() { From 1fb75c1af309d4305866c1bc56b3c148730b2320 Mon Sep 17 00:00:00 2001 From: Simon Laux Date: Sat, 8 Feb 2020 13:07:43 +0100 Subject: [PATCH 11/19] rename dc_chat_get_mute_duration to dc_chat_get_remaining_mute_duration --- deltachat-ffi/deltachat.h | 2 +- deltachat-ffi/src/lib.rs | 4 ++-- python/src/deltachat/chat.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/deltachat-ffi/deltachat.h b/deltachat-ffi/deltachat.h index 634eab881..8a3e57f29 100644 --- a/deltachat-ffi/deltachat.h +++ b/deltachat-ffi/deltachat.h @@ -2952,7 +2952,7 @@ int dc_chat_is_muted (const dc_chat_t* chat); * @param chat The chat object. * @return 0=not muted, -1=forever muted, (x>0)=remaining seconds until the mute is lifted */ -int64_t dc_chat_get_mute_duration (const dc_chat_t* chat); +int64_t dc_chat_get_remaining_mute_duration (const dc_chat_t* chat); /** diff --git a/deltachat-ffi/src/lib.rs b/deltachat-ffi/src/lib.rs index ae69b3a28..6423cf123 100644 --- a/deltachat-ffi/src/lib.rs +++ b/deltachat-ffi/src/lib.rs @@ -2523,9 +2523,9 @@ pub unsafe extern "C" fn dc_chat_is_muted(chat: *mut dc_chat_t) -> libc::c_int { } #[no_mangle] -pub unsafe extern "C" fn dc_chat_get_mute_duration(chat: *mut dc_chat_t) -> i64 { +pub unsafe extern "C" fn dc_chat_get_remaining_mute_duration(chat: *mut dc_chat_t) -> i64 { if chat.is_null() { - eprintln!("ignoring careless call to dc_chat_get_mute_duration()"); + eprintln!("ignoring careless call to dc_chat_get_remaining_mute_duration()"); return 0; } let ffi_chat = &*chat; diff --git a/python/src/deltachat/chat.py b/python/src/deltachat/chat.py index 0b5ff3e40..120059ea9 100644 --- a/python/src/deltachat/chat.py +++ b/python/src/deltachat/chat.py @@ -123,7 +123,7 @@ class Chat(object): :param duration: :returns: Returns the number of seconds the chat is still muted for. (0 for not muted, -1 forever muted) """ - return bool(lib.dc_chat_get_mute_duration(self.id)) + return bool(lib.dc_chat_get_remaining_mute_duration(self.id)) def get_type(self): """ return type of this chat. From 4acb37156f8b8392218ffe4dfbc7b15176962680 Mon Sep 17 00:00:00 2001 From: Simon Laux Date: Sun, 9 Feb 2020 10:39:21 +0100 Subject: [PATCH 12/19] fix building of ffi --- deltachat-ffi/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deltachat-ffi/src/lib.rs b/deltachat-ffi/src/lib.rs index 6423cf123..e8cd0c0dd 100644 --- a/deltachat-ffi/src/lib.rs +++ b/deltachat-ffi/src/lib.rs @@ -1431,7 +1431,7 @@ pub unsafe extern "C" fn dc_chat_set_mute_duration( let ffi_context = &*context; ffi_context .with_inner(|ctx| { - chat::set_muted(ctx, chat_id, muteDuration) + chat::set_muted(ctx, ChatId::new(chat_id), muteDuration) .map(|_| 1) .unwrap_or_log_default(ctx, "Failed to set mute duration") }) From 5f4274b449f06ff6e74e9420dc5004784e588e58 Mon Sep 17 00:00:00 2001 From: Simon Laux Date: Sun, 9 Feb 2020 10:42:32 +0100 Subject: [PATCH 13/19] fix naming --- deltachat-ffi/deltachat.h | 4 ++-- deltachat-ffi/src/lib.rs | 4 ++-- python/src/deltachat/chat.py | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/deltachat-ffi/deltachat.h b/deltachat-ffi/deltachat.h index 8a3e57f29..bb883edaf 100644 --- a/deltachat-ffi/deltachat.h +++ b/deltachat-ffi/deltachat.h @@ -1581,7 +1581,7 @@ int dc_set_chat_profile_image (dc_context_t* context, uint32_t ch /** * Set mute duration of a chat. * - * This value can be checked by the ui upon recieving a new message to decide whether it should trigger an notification. + * This value can be checked by the ui upon receiving a new message to decide whether it should trigger an notification. * * Sends out #DC_EVENT_CHAT_MODIFIED. * @@ -1591,7 +1591,7 @@ int dc_set_chat_profile_image (dc_context_t* context, uint32_t ch * @param context The context as created by dc_context_new(). * @return 1=success, 0=error */ -int dc_chat_set_mute_duration (dc_context_t* context, uint32_t chat_id, int64_t duration); +int dc_set_chat_mute_duration (dc_context_t* context, uint32_t chat_id, int64_t duration); // handle messages diff --git a/deltachat-ffi/src/lib.rs b/deltachat-ffi/src/lib.rs index e8cd0c0dd..d1f2248fa 100644 --- a/deltachat-ffi/src/lib.rs +++ b/deltachat-ffi/src/lib.rs @@ -1412,13 +1412,13 @@ pub unsafe extern "C" fn dc_set_chat_profile_image( } #[no_mangle] -pub unsafe extern "C" fn dc_chat_set_mute_duration( +pub unsafe extern "C" fn dc_set_chat_mute_duration( context: *mut dc_context_t, chat_id: u32, duration: i64, ) -> libc::c_int { if context.is_null() || chat_id <= constants::DC_CHAT_ID_LAST_SPECIAL as u32 { - eprintln!("ignoring careless call to dc_chat_set_mute_duration()"); + eprintln!("ignoring careless call to dc_set_chat_mute_duration()"); return 0; } diff --git a/python/src/deltachat/chat.py b/python/src/deltachat/chat.py index 120059ea9..5227b07f0 100644 --- a/python/src/deltachat/chat.py +++ b/python/src/deltachat/chat.py @@ -108,14 +108,14 @@ class Chat(object): mute_duration = -1 else: mute_duration = duration - return bool(lib.dc_chat_set_mute_duration(self._dc_context, self.id, mute_duration)) + return bool(lib.dc_set_chat_mute_duration(self._dc_context, self.id, mute_duration)) def unmute(self): """ unmutes the chat :returns: """ - return bool(lib.dc_chat_set_mute_duration(self._dc_context, self.id, 0)) + return bool(lib.dc_set_chat_mute_duration(self._dc_context, self.id, 0)) def get_mute_duration(self): """ Returns the number of seconds until the mute of this chat is lifted. From 621f1df91324c77206c5cee10b6c0ba32cc78c18 Mon Sep 17 00:00:00 2001 From: Simon Laux Date: Sun, 9 Feb 2020 12:01:09 +0100 Subject: [PATCH 14/19] rename `MutedUntilTimestamp` to `Until` --- deltachat-ffi/src/lib.rs | 4 ++-- src/chat.rs | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/deltachat-ffi/src/lib.rs b/deltachat-ffi/src/lib.rs index d1f2248fa..c93fced32 100644 --- a/deltachat-ffi/src/lib.rs +++ b/deltachat-ffi/src/lib.rs @@ -1425,7 +1425,7 @@ pub unsafe extern "C" fn dc_set_chat_mute_duration( let muteDuration = match duration { 0 => MuteDuration::NotMuted, -1 => MuteDuration::Forever, - _ => MuteDuration::MutedUntilTimestamp(time() + duration), + _ => MuteDuration::Until(time() + duration), }; let ffi_context = &*context; @@ -2535,7 +2535,7 @@ pub unsafe extern "C" fn dc_chat_get_remaining_mute_duration(chat: *mut dc_chat_ match ffi_chat.chat.mute_duration { MuteDuration::NotMuted => 0, MuteDuration::Forever => -1, - MuteDuration::MutedUntilTimestamp(timestamp) => timestamp - time(), + MuteDuration::Until(timestamp) => timestamp - time(), } } diff --git a/src/chat.rs b/src/chat.rs index 6cc751687..a57b72a65 100644 --- a/src/chat.rs +++ b/src/chat.rs @@ -691,7 +691,7 @@ impl Chat { match self.mute_duration { MuteDuration::NotMuted => false, MuteDuration::Forever => true, - MuteDuration::MutedUntilTimestamp(timestamp) => timestamp > time(), + MuteDuration::Until(timestamp) => timestamp > time(), } } @@ -1921,7 +1921,7 @@ pub fn shall_attach_selfavatar(context: &Context, chat_id: ChatId) -> Result 0, MuteDuration::Forever => -1, - MuteDuration::MutedUntilTimestamp(timestamp) => *timestamp as i64, + MuteDuration::Until(timestamp) => *timestamp as i64, }; let val = rusqlite::types::Value::Integer(duration as i64); let out = rusqlite::types::ToSqlOutput::Owned(val); @@ -1949,7 +1949,7 @@ impl rusqlite::types::FromSql for MuteDuration { if val <= time() { MuteDuration::NotMuted } else { - MuteDuration::MutedUntilTimestamp(val) + MuteDuration::Until(val) } } } @@ -2880,7 +2880,7 @@ mod tests { set_muted( &t.ctx, chat_id, - MuteDuration::MutedUntilTimestamp(time() + 3600), + MuteDuration::Until(time() + 3600), ) .unwrap(); assert_eq!( @@ -2891,7 +2891,7 @@ mod tests { set_muted( &t.ctx, chat_id, - MuteDuration::MutedUntilTimestamp(time() - 3600), + MuteDuration::Until(time() - 3600), ) .unwrap(); assert_eq!( From d73d021e3c33f7efe2d05d6472c9188f78681852 Mon Sep 17 00:00:00 2001 From: Simon Laux Date: Sun, 9 Feb 2020 12:25:47 +0100 Subject: [PATCH 15/19] cargo fmt --- src/chat.rs | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) diff --git a/src/chat.rs b/src/chat.rs index a57b72a65..13fc7a4fc 100644 --- a/src/chat.rs +++ b/src/chat.rs @@ -1921,7 +1921,7 @@ pub fn shall_attach_selfavatar(context: &Context, chat_id: ChatId) -> Result Date: Sun, 9 Feb 2020 12:36:37 +0100 Subject: [PATCH 16/19] fix python formatting --- python/src/deltachat/chat.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/python/src/deltachat/chat.py b/python/src/deltachat/chat.py index 5227b07f0..30c8ff53a 100644 --- a/python/src/deltachat/chat.py +++ b/python/src/deltachat/chat.py @@ -5,7 +5,6 @@ import calendar import json from datetime import datetime import os -import time from .cutil import as_dc_charpointer, from_dc_charpointer, iter_array from .capi import lib, ffi from . import const @@ -97,12 +96,12 @@ class Chat(object): """ name = as_dc_charpointer(name) return lib.dc_set_chat_name(self._dc_context, self.id, name) - + def mute(self, duration=None): """ mutes the chat :param duration: Number of seconds to mute the chat for. None to mute until unmuted again. - :returns: + :returns: """ if duration is None: mute_duration = -1 @@ -113,14 +112,14 @@ class Chat(object): def unmute(self): """ unmutes the chat - :returns: + :returns: """ return bool(lib.dc_set_chat_mute_duration(self._dc_context, self.id, 0)) def get_mute_duration(self): """ Returns the number of seconds until the mute of this chat is lifted. - :param duration: + :param duration: :returns: Returns the number of seconds the chat is still muted for. (0 for not muted, -1 forever muted) """ return bool(lib.dc_chat_get_remaining_mute_duration(self.id)) From 4aebd678c353b5e0ae855524f94b3da740f81ace Mon Sep 17 00:00:00 2001 From: Floris Bruynooghe Date: Sun, 9 Feb 2020 12:37:13 +0100 Subject: [PATCH 17/19] Use SystemTime instead of i64 This clarifies some behaviour with negative times and propagates errors a bit better around places. --- deltachat-ffi/src/lib.rs | 22 +++++++++----- deltachat-ffi/src/tools.rs | 8 ----- src/chat.rs | 60 +++++++++++++++++++++++--------------- 3 files changed, 50 insertions(+), 40 deletions(-) delete mode 100644 deltachat-ffi/src/tools.rs diff --git a/deltachat-ffi/src/lib.rs b/deltachat-ffi/src/lib.rs index c93fced32..70fad4937 100644 --- a/deltachat-ffi/src/lib.rs +++ b/deltachat-ffi/src/lib.rs @@ -20,6 +20,7 @@ use std::fmt::Write; use std::ptr; use std::str::FromStr; use std::sync::RwLock; +use std::time::{Duration, SystemTime}; use libc::uintptr_t; use num_traits::{FromPrimitive, ToPrimitive}; @@ -34,9 +35,6 @@ use deltachat::message::MsgId; use deltachat::stock::StockMessage; use deltachat::*; -mod tools; -use crate::tools::time; - mod dc_array; mod string; @@ -1421,14 +1419,18 @@ pub unsafe extern "C" fn dc_set_chat_mute_duration( eprintln!("ignoring careless call to dc_set_chat_mute_duration()"); return 0; } - + let ffi_context = &*context; let muteDuration = match duration { 0 => MuteDuration::NotMuted, -1 => MuteDuration::Forever, - _ => MuteDuration::Until(time() + duration), + n if n > 0 => MuteDuration::Until(SystemTime::now() + Duration::from_secs(duration as u64)), + _ => { + ffi_context.warning( + "dc_chat_set_mute_duration(): Can not use negative duration other than -1", + ); + return 0; + } }; - - let ffi_context = &*context; ffi_context .with_inner(|ctx| { chat::set_muted(ctx, ChatId::new(chat_id), muteDuration) @@ -2532,10 +2534,14 @@ pub unsafe extern "C" fn dc_chat_get_remaining_mute_duration(chat: *mut dc_chat_ if !ffi_chat.chat.is_muted() { return 0; } + // If the chat was muted to before the epoch, it is not muted. match ffi_chat.chat.mute_duration { MuteDuration::NotMuted => 0, MuteDuration::Forever => -1, - MuteDuration::Until(timestamp) => timestamp - time(), + MuteDuration::Until(when) => when + .duration_since(SystemTime::UNIX_EPOCH) + .map(|d| d.as_secs() as i64) + .unwrap_or(0), } } diff --git a/deltachat-ffi/src/tools.rs b/deltachat-ffi/src/tools.rs deleted file mode 100644 index e7f7e8bb7..000000000 --- a/deltachat-ffi/src/tools.rs +++ /dev/null @@ -1,8 +0,0 @@ -use std::time::SystemTime; - -pub(crate) fn time() -> i64 { - SystemTime::now() - .duration_since(SystemTime::UNIX_EPOCH) - .unwrap_or_default() - .as_secs() as i64 -} diff --git a/src/chat.rs b/src/chat.rs index 13fc7a4fc..02f81a661 100644 --- a/src/chat.rs +++ b/src/chat.rs @@ -1,6 +1,8 @@ //! # Chat module +use std::convert::TryFrom; use std::path::{Path, PathBuf}; +use std::time::{Duration, SystemTime}; use itertools::Itertools; use num_traits::FromPrimitive; @@ -691,7 +693,7 @@ impl Chat { match self.mute_duration { MuteDuration::NotMuted => false, MuteDuration::Forever => true, - MuteDuration::Until(timestamp) => timestamp > time(), + MuteDuration::Until(when) => when > SystemTime::now(), } } @@ -1921,17 +1923,23 @@ pub fn shall_attach_selfavatar(context: &Context, chat_id: ChatId) -> Result rusqlite::Result { - let duration = match &self { + let duration: i64 = match &self { MuteDuration::NotMuted => 0, MuteDuration::Forever => -1, - MuteDuration::Until(timestamp) => *timestamp as i64, + MuteDuration::Until(when) => { + let duration = when + .duration_since(SystemTime::UNIX_EPOCH) + .map_err(|err| rusqlite::Error::ToSqlConversionFailure(Box::new(err)))?; + i64::try_from(duration.as_secs()) + .map_err(|err| rusqlite::Error::ToSqlConversionFailure(Box::new(err)))? + } }; - let val = rusqlite::types::Value::Integer(duration as i64); + let val = rusqlite::types::Value::Integer(duration); let out = rusqlite::types::ToSqlOutput::Owned(val); Ok(out) } @@ -1939,22 +1947,17 @@ impl rusqlite::types::ToSql for MuteDuration { impl rusqlite::types::FromSql for MuteDuration { fn column_result(value: rusqlite::types::ValueRef) -> rusqlite::types::FromSqlResult { - // Would be nice if we could use match here, but alas. - i64::column_result(value).and_then(|val| { - Ok({ - match val { - 0 => MuteDuration::NotMuted, - -1 => MuteDuration::Forever, - _ => { - if val <= time() { - MuteDuration::NotMuted - } else { - MuteDuration::Until(val) - } - } - } - }) - }) + // Negative values other than -1 should not be in the + // database. If found they'll be NotMuted. + match i64::column_result(value)? { + 0 => Ok(MuteDuration::NotMuted), + -1 => Ok(MuteDuration::Forever), + n if n > 0 => match SystemTime::UNIX_EPOCH.checked_add(Duration::from_secs(n as u64)) { + Some(t) => Ok(MuteDuration::Until(t)), + None => Err(rusqlite::types::FromSqlError::OutOfRange(n)), + }, + _ => Ok(MuteDuration::NotMuted), + } } } @@ -1973,7 +1976,6 @@ pub fn set_muted(context: &Context, chat_id: ChatId, duration: MuteDuration) -> } else { bail!("Failed to set name"); } - Ok(()) } @@ -2877,13 +2879,23 @@ mod tests { false ); // Timed in the future - set_muted(&t.ctx, chat_id, MuteDuration::Until(time() + 3600)).unwrap(); + set_muted( + &t.ctx, + chat_id, + MuteDuration::Until(SystemTime::now() + Duration::from_secs(3600)), + ) + .unwrap(); assert_eq!( Chat::load_from_db(&t.ctx, chat_id).unwrap().is_muted(), true ); // Time in the past - set_muted(&t.ctx, chat_id, MuteDuration::Until(time() - 3600)).unwrap(); + set_muted( + &t.ctx, + chat_id, + MuteDuration::Until(SystemTime::now() - Duration::from_secs(3600)), + ) + .unwrap(); assert_eq!( Chat::load_from_db(&t.ctx, chat_id).unwrap().is_muted(), false From 0242322d247442e2365634d0aec39b426950ffad Mon Sep 17 00:00:00 2001 From: Floris Bruynooghe Date: Sun, 9 Feb 2020 17:59:52 +0100 Subject: [PATCH 18/19] Tweak error halding a little - Python should raise exceptions on error. Not return False. - Negative durations are nonense. - ChatID validity is already checked by the Rust API, let's not duplicate. --- deltachat-ffi/src/lib.rs | 2 +- python/src/deltachat/chat.py | 6 ++++-- python/tests/test_account.py | 4 ++-- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/deltachat-ffi/src/lib.rs b/deltachat-ffi/src/lib.rs index 70fad4937..6d57a1906 100644 --- a/deltachat-ffi/src/lib.rs +++ b/deltachat-ffi/src/lib.rs @@ -1415,7 +1415,7 @@ pub unsafe extern "C" fn dc_set_chat_mute_duration( chat_id: u32, duration: i64, ) -> libc::c_int { - if context.is_null() || chat_id <= constants::DC_CHAT_ID_LAST_SPECIAL as u32 { + if context.is_null() { eprintln!("ignoring careless call to dc_set_chat_mute_duration()"); return 0; } diff --git a/python/src/deltachat/chat.py b/python/src/deltachat/chat.py index 30c8ff53a..4ad437bbf 100644 --- a/python/src/deltachat/chat.py +++ b/python/src/deltachat/chat.py @@ -101,13 +101,15 @@ class Chat(object): """ mutes the chat :param duration: Number of seconds to mute the chat for. None to mute until unmuted again. - :returns: + :returns: None """ if duration is None: mute_duration = -1 else: mute_duration = duration - return bool(lib.dc_set_chat_mute_duration(self._dc_context, self.id, mute_duration)) + ret = lib.dc_set_chat_mute_duration(self._dc_context, self.id, mute_duration) + if not bool(ret): + raise ValueError("Call to dc_set_chat_mute_duration failed") def unmute(self): """ unmutes the chat diff --git a/python/tests/test_account.py b/python/tests/test_account.py index 0ae067675..4d99683ad 100644 --- a/python/tests/test_account.py +++ b/python/tests/test_account.py @@ -229,8 +229,8 @@ class TestOfflineChat: assert not chat.is_muted() assert chat.mute(50) assert chat.is_muted() - assert chat.mute(-51) - assert not chat.is_muted() + with pytest.raises(ValueError): + chat.mute(-51) def test_delete_and_send_fails(self, ac1, chat1): chat1.delete() From e525c42c7d31baf13edf2ed65177fcee759a04df Mon Sep 17 00:00:00 2001 From: Floris Bruynooghe Date: Sun, 9 Feb 2020 19:11:30 +0100 Subject: [PATCH 19/19] Unmute should also raise exceptions And tests should not care about return values. --- python/src/deltachat/chat.py | 6 ++++-- python/tests/test_account.py | 6 +++--- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/python/src/deltachat/chat.py b/python/src/deltachat/chat.py index 4ad437bbf..c2517f156 100644 --- a/python/src/deltachat/chat.py +++ b/python/src/deltachat/chat.py @@ -114,9 +114,11 @@ class Chat(object): def unmute(self): """ unmutes the chat - :returns: + :returns: None """ - return bool(lib.dc_set_chat_mute_duration(self._dc_context, self.id, 0)) + ret = lib.dc_set_chat_mute_duration(self._dc_context, self.id, 0) + if not bool(ret): + raise ValueError("Failed to unmute chat") def get_mute_duration(self): """ Returns the number of seconds until the mute of this chat is lifted. diff --git a/python/tests/test_account.py b/python/tests/test_account.py index 4d99683ad..19ed817fc 100644 --- a/python/tests/test_account.py +++ b/python/tests/test_account.py @@ -223,11 +223,11 @@ class TestOfflineChat: def test_mute(self, ac1): chat = ac1.create_group_chat(name="title1") assert not chat.is_muted() - assert chat.mute() + chat.mute() assert chat.is_muted() - assert chat.unmute() + chat.unmute() assert not chat.is_muted() - assert chat.mute(50) + chat.mute(50) assert chat.is_muted() with pytest.raises(ValueError): chat.mute(-51)