2
2
3
3
use std:: collections:: { BTreeMap , BTreeSet } ;
4
4
5
- use anyhow:: { Context as _, Result } ;
5
+ use anyhow:: { ensure , Context as _, Result } ;
6
6
use pgp:: types:: PublicKeyTrait ;
7
7
use serde:: Serialize ;
8
8
@@ -30,7 +30,8 @@ struct Statistics {
30
30
key_created : i64 ,
31
31
chat_numbers : ChatNumbers ,
32
32
self_reporting_id : String ,
33
- contact_infos : Vec < ContactInfo > ,
33
+ contact_stats : Vec < ContactStat > ,
34
+ message_stats : MessageStats ,
34
35
}
35
36
#[ derive( Default , Serialize ) ]
36
37
struct ChatNumbers {
@@ -52,7 +53,7 @@ enum VerifiedStatus {
52
53
}
53
54
54
55
#[ derive( Serialize ) ]
55
- struct ContactInfo {
56
+ struct ContactStat {
56
57
#[ serde( skip_serializing) ]
57
58
id : ContactId ,
58
59
@@ -61,16 +62,16 @@ struct ContactInfo {
61
62
direct_chat : bool ,
62
63
last_seen : u64 ,
63
64
64
- //new: bool, // TODO
65
65
#[ serde( skip_serializing_if = "Option::is_none" ) ]
66
66
transitive_chain : Option < u32 > ,
67
+ //new: bool, // TODO
67
68
}
68
69
69
- async fn get_contact_infos ( context : & Context ) -> Result < Vec < ContactInfo > > {
70
+ async fn get_contact_stats ( context : & Context ) -> Result < Vec < ContactStat > > {
70
71
let mut verified_by_map: BTreeMap < ContactId , ContactId > = BTreeMap :: new ( ) ;
71
72
let mut bot_ids: BTreeSet < ContactId > = BTreeSet :: new ( ) ;
72
73
73
- let mut contacts: Vec < ContactInfo > = context
74
+ let mut contacts: Vec < ContactStat > = context
74
75
. sql
75
76
. query_map (
76
77
"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>> {
98
99
bot_ids. insert ( id) ;
99
100
}
100
101
101
- Ok ( ContactInfo {
102
+ Ok ( ContactStat {
102
103
id,
103
104
verified,
104
105
bot,
@@ -165,40 +166,133 @@ async fn get_contact_infos(context: &Context) -> Result<Vec<ContactInfo>> {
165
166
Ok ( contacts)
166
167
}
167
168
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
+
168
241
/// Sends a message with statistics about the usage of Delta Chat,
169
242
/// if the last time such a message was sent
170
243
/// was more than a week ago.
171
244
///
172
245
/// On the other end, a bot will receive the message and make it available
173
246
/// 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 > > {
175
248
//#[cfg(target_os = "android")] TODO
176
249
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 ?) ) ;
187
254
}
188
255
}
189
- Ok ( ( ) )
256
+ Ok ( None )
190
257
}
191
258
192
259
async fn send_self_report ( context : & Context ) -> Result < ChatId > {
193
260
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.
196
263
context
197
264
. set_config_internal ( Config :: LastSelfReportSent , Some ( & time ( ) . to_string ( ) ) )
198
265
. await
199
266
. log_err ( context)
200
267
. ok ( ) ;
201
268
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 > {
202
296
let contact_id: ContactId = * import_vcard ( context, SELF_REPORTING_BOT_VCARD )
203
297
. await ?
204
298
. first ( )
@@ -226,27 +320,6 @@ async fn send_self_report(context: &Context) -> Result<ChatId> {
226
320
)
227
321
. await ?;
228
322
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
-
250
323
Ok ( chat_id)
251
324
}
252
325
@@ -355,7 +428,8 @@ async fn get_self_report(context: &Context) -> Result<String> {
355
428
key_created,
356
429
chat_numbers,
357
430
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 ?,
359
433
} ;
360
434
361
435
Ok ( serde_json:: to_string_pretty ( & statistics) ?)
0 commit comments