@@ -4,6 +4,7 @@ use std::{collections::HashMap, str::FromStr};
4
4
5
5
use anyhow:: { anyhow, bail, ensure, Context , Result } ;
6
6
pub use deltachat:: accounts:: Accounts ;
7
+ use deltachat:: qr:: Qr ;
7
8
use deltachat:: {
8
9
chat:: {
9
10
self , add_contact_to_chat, forward_msgs, get_chat_media, get_chat_msgs, get_chat_msgs_ex,
@@ -29,7 +30,8 @@ use deltachat::{
29
30
webxdc:: StatusUpdateSerial ,
30
31
} ;
31
32
use sanitize_filename:: is_sanitized;
32
- use tokio:: { fs, sync:: RwLock } ;
33
+ use tokio:: fs;
34
+ use tokio:: sync:: { watch, Mutex , RwLock } ;
33
35
use walkdir:: WalkDir ;
34
36
use yerpc:: rpc;
35
37
@@ -57,21 +59,45 @@ use self::types::{
57
59
use crate :: api:: types:: chat_list:: { get_chat_list_item_by_id, ChatListItemFetchResult } ;
58
60
use crate :: api:: types:: qr:: QrObject ;
59
61
62
+ #[ derive( Debug ) ]
63
+ struct AccountState {
64
+ /// The Qr code for current [`CommandApi::provide_backup`] call.
65
+ ///
66
+ /// If there currently is a call to [`CommandApi::provide_backup`] this will be
67
+ /// `Pending` or `Ready`, otherwise `NoProvider`.
68
+ backup_provider_qr : watch:: Sender < ProviderQr > ,
69
+ }
70
+
71
+ impl Default for AccountState {
72
+ fn default ( ) -> Self {
73
+ let ( tx, _rx) = watch:: channel ( ProviderQr :: NoProvider ) ;
74
+ Self {
75
+ backup_provider_qr : tx,
76
+ }
77
+ }
78
+ }
79
+
60
80
#[ derive( Clone , Debug ) ]
61
81
pub struct CommandApi {
62
82
pub ( crate ) accounts : Arc < RwLock < Accounts > > ,
83
+
84
+ states : Arc < Mutex < BTreeMap < u32 , AccountState > > > ,
63
85
}
64
86
65
87
impl CommandApi {
66
88
pub fn new ( accounts : Accounts ) -> Self {
67
89
CommandApi {
68
90
accounts : Arc :: new ( RwLock :: new ( accounts) ) ,
91
+ states : Arc :: new ( Mutex :: new ( BTreeMap :: new ( ) ) ) ,
69
92
}
70
93
}
71
94
72
95
#[ allow( dead_code) ]
73
96
pub fn from_arc ( accounts : Arc < RwLock < Accounts > > ) -> Self {
74
- CommandApi { accounts }
97
+ CommandApi {
98
+ accounts,
99
+ states : Arc :: new ( Mutex :: new ( BTreeMap :: new ( ) ) ) ,
100
+ }
75
101
}
76
102
77
103
async fn get_context ( & self , id : u32 ) -> Result < deltachat:: context:: Context > {
@@ -83,6 +109,38 @@ impl CommandApi {
83
109
. ok_or_else ( || anyhow ! ( "account with id {} not found" , id) ) ?;
84
110
Ok ( sc)
85
111
}
112
+
113
+ async fn with_state < F , T > ( & self , id : u32 , with_state : F ) -> T
114
+ where
115
+ F : FnOnce ( & AccountState ) -> T ,
116
+ {
117
+ let mut states = self . states . lock ( ) . await ;
118
+ let state = states. entry ( id) . or_insert_with ( Default :: default) ;
119
+ with_state ( state)
120
+ }
121
+
122
+ async fn inner_get_backup_qr ( & self , account_id : u32 ) -> Result < Qr > {
123
+ let mut receiver = self
124
+ . with_state ( account_id, |state| state. backup_provider_qr . subscribe ( ) )
125
+ . await ;
126
+
127
+ let val: ProviderQr = receiver. borrow_and_update ( ) . clone ( ) ;
128
+ match val {
129
+ ProviderQr :: NoProvider => bail ! ( "No backup being provided" ) ,
130
+ ProviderQr :: Pending => loop {
131
+ if receiver. changed ( ) . await . is_err ( ) {
132
+ bail ! ( "No backup being provided (account state dropped)" ) ;
133
+ }
134
+ let val: ProviderQr = receiver. borrow ( ) . clone ( ) ;
135
+ match val {
136
+ ProviderQr :: NoProvider => bail ! ( "No backup being provided" ) ,
137
+ ProviderQr :: Pending => continue ,
138
+ ProviderQr :: Ready ( qr) => break Ok ( qr) ,
139
+ } ;
140
+ } ,
141
+ ProviderQr :: Ready ( qr) => Ok ( qr) ,
142
+ }
143
+ }
86
144
}
87
145
88
146
#[ rpc( all_positional, ts_outdir = "typescript/generated" ) ]
@@ -115,7 +173,13 @@ impl CommandApi {
115
173
}
116
174
117
175
async fn remove_account ( & self , account_id : u32 ) -> Result < ( ) > {
118
- self . accounts . write ( ) . await . remove_account ( account_id) . await
176
+ self . accounts
177
+ . write ( )
178
+ . await
179
+ . remove_account ( account_id)
180
+ . await ?;
181
+ self . states . lock ( ) . await . remove ( & account_id) ;
182
+ Ok ( ( ) )
119
183
}
120
184
121
185
async fn get_all_account_ids ( & self ) -> Vec < u32 > {
@@ -1358,31 +1422,35 @@ impl CommandApi {
1358
1422
///
1359
1423
/// This **stops IO** while it is running.
1360
1424
///
1361
- /// Returns once a remote device has retrieved the backup.
1425
+ /// Returns once a remote device has retrieved the backup, or is cancelled .
1362
1426
async fn provide_backup ( & self , account_id : u32 ) -> Result < ( ) > {
1363
1427
let ctx = self . get_context ( account_id) . await ?;
1364
- ctx. stop_io ( ) . await ;
1365
- let provider = match imex:: BackupProvider :: prepare ( & ctx) . await {
1366
- Ok ( provider) => provider,
1367
- Err ( err) => {
1368
- ctx. start_io ( ) . await ;
1369
- return Err ( err) ;
1370
- }
1371
- } ;
1372
- let res = provider. await ;
1373
- ctx. start_io ( ) . await ;
1374
- res
1428
+ self . with_state ( account_id, |state| {
1429
+ state. backup_provider_qr . send_replace ( ProviderQr :: Pending ) ;
1430
+ } )
1431
+ . await ;
1432
+
1433
+ let provider = imex:: BackupProvider :: prepare ( & ctx) . await ?;
1434
+ self . with_state ( account_id, |state| {
1435
+ state
1436
+ . backup_provider_qr
1437
+ . send_replace ( ProviderQr :: Ready ( provider. qr ( ) ) ) ;
1438
+ } )
1439
+ . await ;
1440
+
1441
+ provider. await
1375
1442
}
1376
1443
1377
1444
/// Returns the text of the QR code for the running [`CommandApi::provide_backup`].
1378
1445
///
1379
1446
/// This QR code text can be used in [`CommandApi::get_backup`] on a second device to
1380
1447
/// retrieve the backup and setup this second device.
1448
+ ///
1449
+ /// This call will fail if there is currently no concurrent call to
1450
+ /// [`CommandApi::provide_backup`]. This call may block if the QR code is not yet
1451
+ /// ready.
1381
1452
async fn get_backup_qr ( & self , account_id : u32 ) -> Result < String > {
1382
- let ctx = self . get_context ( account_id) . await ?;
1383
- let qr = ctx
1384
- . backup_export_qr ( )
1385
- . ok_or ( anyhow ! ( "no backup being exported" ) ) ?;
1453
+ let qr = self . inner_get_backup_qr ( account_id) . await ?;
1386
1454
qr:: format_backup ( & qr)
1387
1455
}
1388
1456
@@ -1391,12 +1459,14 @@ impl CommandApi {
1391
1459
/// This QR code can be used in [`CommandApi::get_backup`] on a second device to
1392
1460
/// retrieve the backup and setup this second device.
1393
1461
///
1462
+ /// This call will fail if there is currently no concurrent call to
1463
+ /// [`CommandApi::provide_backup`]. This call may block if the QR code is not yet
1464
+ /// ready.
1465
+ ///
1394
1466
/// Returns the QR code rendered as an SVG image.
1395
1467
async fn get_backup_qr_svg ( & self , account_id : u32 ) -> Result < String > {
1396
1468
let ctx = self . get_context ( account_id) . await ?;
1397
- let qr = ctx
1398
- . backup_export_qr ( )
1399
- . ok_or ( anyhow ! ( "no backup being exported" ) ) ?;
1469
+ let qr = self . inner_get_backup_qr ( account_id) . await ?;
1400
1470
generate_backup_qr ( & ctx, & qr) . await
1401
1471
}
1402
1472
@@ -1900,3 +1970,15 @@ async fn get_config(
1900
1970
. await
1901
1971
}
1902
1972
}
1973
+
1974
+ /// Whether a QR code for a BackupProvider is currently available.
1975
+ #[ allow( clippy:: large_enum_variant) ]
1976
+ #[ derive( Clone , Debug ) ]
1977
+ enum ProviderQr {
1978
+ /// There is no provider, asking for a QR is an error.
1979
+ NoProvider ,
1980
+ /// There is a provider, the QR code is pending.
1981
+ Pending ,
1982
+ /// There is a provider and QR code.
1983
+ Ready ( Qr ) ,
1984
+ }
0 commit comments