22
33use std:: collections:: { BTreeMap , BTreeSet } ;
44
5- use anyhow:: { Context as _, Result } ;
5+ use anyhow:: { ensure , Context as _, Result } ;
66use pgp:: types:: PublicKeyTrait ;
77use serde:: Serialize ;
88
@@ -30,7 +30,8 @@ struct Statistics {
3030 key_created : i64 ,
3131 chat_numbers : ChatNumbers ,
3232 self_reporting_id : String ,
33- contact_infos : Vec < ContactInfo > ,
33+ contact_stats : Vec < ContactStat > ,
34+ message_stats : MessageStats ,
3435}
3536#[ derive( Default , Serialize ) ]
3637struct ChatNumbers {
@@ -52,7 +53,7 @@ enum VerifiedStatus {
5253}
5354
5455#[ derive( Serialize ) ]
55- struct ContactInfo {
56+ struct ContactStat {
5657 #[ serde( skip_serializing) ]
5758 id : ContactId ,
5859
@@ -61,16 +62,16 @@ struct ContactInfo {
6162 direct_chat : bool ,
6263 last_seen : u64 ,
6364
64- //new: bool, // TODO
6565 #[ serde( skip_serializing_if = "Option::is_none" ) ]
6666 transitive_chain : Option < u32 > ,
67+ //new: bool, // TODO
6768}
6869
69- async fn get_contact_infos ( context : & Context ) -> Result < Vec < ContactInfo > > {
70+ async fn get_contact_stats ( context : & Context ) -> Result < Vec < ContactStat > > {
7071 let mut verified_by_map: BTreeMap < ContactId , ContactId > = BTreeMap :: new ( ) ;
7172 let mut bot_ids: BTreeSet < ContactId > = BTreeSet :: new ( ) ;
7273
73- let mut contacts: Vec < ContactInfo > = context
74+ let mut contacts: Vec < ContactStat > = context
7475 . sql
7576 . query_map (
7677 "SELECT id, fingerprint<>'', verifier, last_seen, is_bot FROM contacts c
@@ -98,7 +99,7 @@ async fn get_contact_infos(context: &Context) -> Result<Vec<ContactInfo>> {
9899 bot_ids. insert ( id) ;
99100 }
100101
101- Ok ( ContactInfo {
102+ Ok ( ContactStat {
102103 id,
103104 verified,
104105 bot,
@@ -165,40 +166,133 @@ async fn get_contact_infos(context: &Context) -> Result<Vec<ContactInfo>> {
165166 Ok ( contacts)
166167}
167168
169+ #[ derive( Serialize ) ]
170+ struct MessageStats {
171+ to_verified : u32 ,
172+ unverified_encrypted : u32 ,
173+ unencrypted : u32 ,
174+ }
175+
176+ async fn get_message_stats ( context : & Context ) -> Result < MessageStats > {
177+ let enabled_ts: i64 = context
178+ . get_config_i64 ( Config :: SelfReportingEnabledTimestamp )
179+ . await ?;
180+ ensure ! ( enabled_ts > 0 , "Enabled Timestamp missing" ) ;
181+
182+ let selfreporting_bot_chat_id = get_selfreporting_bot ( context) . await ?;
183+
184+ let trans_fn = |t : & mut rusqlite:: Transaction | {
185+ t. pragma_update ( None , "query_only" , "0" ) ?;
186+ t. execute (
187+ "CREATE TEMP TABLE temp.verified_chats (
188+ id INTEGER PRIMARY KEY
189+ ) STRICT" ,
190+ ( ) ,
191+ ) ?;
192+
193+ t. execute (
194+ "INSERT INTO temp.verified_chats
195+ SELECT id FROM chats
196+ WHERE protected=1 AND id>9" ,
197+ ( ) ,
198+ ) ?;
199+
200+ let to_verified = t. query_row (
201+ "SELECT COUNT(*) FROM msgs
202+ WHERE chat_id IN temp.verified_chats
203+ AND chat_id<>? AND id>9 AND timestamp_sent>?" ,
204+ ( selfreporting_bot_chat_id, enabled_ts) ,
205+ |row| row. get ( 0 ) ,
206+ ) ?;
207+
208+ let unverified_encrypted = t. query_row (
209+ "SELECT COUNT(*) FROM msgs
210+ WHERE chat_id not IN temp.verified_chats
211+ AND (param GLOB '*\n c=1*' OR param GLOB 'c=1*')
212+ AND chat_id<>? AND id>9 AND timestamp_sent>?" ,
213+ ( selfreporting_bot_chat_id, enabled_ts) ,
214+ |row| row. get ( 0 ) ,
215+ ) ?;
216+
217+ let unencrypted = t. query_row (
218+ "SELECT COUNT(*) FROM msgs
219+ WHERE chat_id not IN temp.verified_chats
220+ AND NOT (param GLOB '*\n c=1*' OR param GLOB 'c=1*')
221+ AND chat_id<>? AND id>9 AND timestamp_sent>=?" ,
222+ ( selfreporting_bot_chat_id, enabled_ts) ,
223+ |row| row. get ( 0 ) ,
224+ ) ?;
225+
226+ t. execute ( "DROP TABLE temp.verified_chats" , ( ) ) ?;
227+
228+ Ok ( MessageStats {
229+ to_verified,
230+ unverified_encrypted,
231+ unencrypted,
232+ } )
233+ } ;
234+
235+ let query_only = true ;
236+ let message_stats: MessageStats = context. sql . transaction_ex ( query_only, trans_fn) . await ?;
237+
238+ Ok ( message_stats)
239+ }
240+
168241/// Sends a message with statistics about the usage of Delta Chat,
169242/// if the last time such a message was sent
170243/// was more than a week ago.
171244///
172245/// On the other end, a bot will receive the message and make it available
173246/// to Delta Chat's developers.
174- pub async fn maybe_send_self_report ( context : & Context ) -> Result < ( ) > {
247+ pub async fn maybe_send_self_report ( context : & Context ) -> Result < Option < ChatId > > {
175248 //#[cfg(target_os = "android")] TODO
176249 if context. get_config_bool ( Config :: SelfReporting ) . await ? {
177- match context. get_config_i64 ( Config :: LastSelfReportSent ) . await {
178- Ok ( last_selfreport_time) => {
179- let next_selfreport_time = last_selfreport_time. saturating_add ( 30 ) ; // TODO increase to 1 day or 1 week
180- if next_selfreport_time <= time ( ) {
181- send_self_report ( context) . await ?;
182- }
183- }
184- Err ( err) => {
185- warn ! ( context, "Failed to get last self_reporting time: {}" , err) ;
186- }
250+ let last_selfreport_time = context. get_config_i64 ( Config :: LastSelfReportSent ) . await ?;
251+ let next_selfreport_time = last_selfreport_time. saturating_add ( 30 ) ; // TODO increase to 1 day or 1 week
252+ if next_selfreport_time <= time ( ) {
253+ return Ok ( Some ( send_self_report ( context) . await ?) ) ;
187254 }
188255 }
189- Ok ( ( ) )
256+ Ok ( None )
190257}
191258
192259async fn send_self_report ( context : & Context ) -> Result < ChatId > {
193260 info ! ( context, "Sending self report." ) ;
194- // Setting `Config::LastHousekeeping` at the beginning avoids endless loops when things do not
195- // work out for whatever reason or are interrupted by the OS .
261+ // Setting this config at the beginning avoids endless loops when things do not
262+ // work out for whatever reason.
196263 context
197264 . set_config_internal ( Config :: LastSelfReportSent , Some ( & time ( ) . to_string ( ) ) )
198265 . await
199266 . log_err ( context)
200267 . ok ( ) ;
201268
269+ let chat_id = get_selfreporting_bot ( context) . await ?;
270+
271+ let mut msg = Message :: new ( Viewtype :: File ) ;
272+ msg. set_text (
273+ "The attachment contains anonymous usage statistics, \
274+ because you enabled this in the settings. \
275+ This helps us improve the security of Delta Chat. \
276+ See TODO[blog post] for more information."
277+ . to_string ( ) ,
278+ ) ;
279+ msg. set_file_from_bytes (
280+ context,
281+ "statistics.txt" ,
282+ get_self_report ( context) . await ?. as_bytes ( ) ,
283+ Some ( "text/plain" ) ,
284+ ) ?;
285+
286+ crate :: chat:: send_msg ( context, chat_id, & mut msg)
287+ . await
288+ . context ( "Failed to send self_reporting message" )
289+ . log_err ( context)
290+ . ok ( ) ;
291+
292+ Ok ( chat_id)
293+ }
294+
295+ async fn get_selfreporting_bot ( context : & Context ) -> Result < ChatId , anyhow:: Error > {
202296 let contact_id: ContactId = * import_vcard ( context, SELF_REPORTING_BOT_VCARD )
203297 . await ?
204298 . first ( )
@@ -226,27 +320,6 @@ async fn send_self_report(context: &Context) -> Result<ChatId> {
226320 )
227321 . await ?;
228322
229- let mut msg = Message :: new ( Viewtype :: File ) ;
230- msg. set_text (
231- "The attachment contains anonymous usage statistics, \
232- because you enabled this in the settings. \
233- This helps us improve the security of Delta Chat. \
234- See TODO[blog post] for more information."
235- . to_string ( ) ,
236- ) ;
237- msg. set_file_from_bytes (
238- context,
239- "statistics.txt" ,
240- get_self_report ( context) . await ?. as_bytes ( ) ,
241- Some ( "text/plain" ) ,
242- ) ?;
243-
244- crate :: chat:: send_msg ( context, chat_id, & mut msg)
245- . await
246- . context ( "Failed to send self_reporting message" )
247- . log_err ( context)
248- . ok ( ) ;
249-
250323 Ok ( chat_id)
251324}
252325
@@ -355,7 +428,8 @@ async fn get_self_report(context: &Context) -> Result<String> {
355428 key_created,
356429 chat_numbers,
357430 self_reporting_id,
358- contact_infos : get_contact_infos ( context) . await ?,
431+ contact_stats : get_contact_stats ( context) . await ?,
432+ message_stats : get_message_stats ( context) . await ?,
359433 } ;
360434
361435 Ok ( serde_json:: to_string_pretty ( & statistics) ?)
0 commit comments