Skip to content

Commit e985588

Browse files
authored
ref(jsonrpc): Getting backup provider QR code now blocks (#4198)
This changes the JSON-RPC APIs to get a QR code from the backup provider to block. It means once you have a (blocking) call to provide_backup() you can call get_backup_qr() or get_backup_qr_svg() and they will block until the QR code is available. Calling get_backup_qr() or get_backup_qr_svg() when there is no backup provider will immediately error.
1 parent 7ec3a1a commit e985588

File tree

3 files changed

+107
-54
lines changed

3 files changed

+107
-54
lines changed

deltachat-jsonrpc/src/api/mod.rs

Lines changed: 104 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ use std::{collections::HashMap, str::FromStr};
44

55
use anyhow::{anyhow, bail, ensure, Context, Result};
66
pub use deltachat::accounts::Accounts;
7+
use deltachat::qr::Qr;
78
use deltachat::{
89
chat::{
910
self, add_contact_to_chat, forward_msgs, get_chat_media, get_chat_msgs, get_chat_msgs_ex,
@@ -29,7 +30,8 @@ use deltachat::{
2930
webxdc::StatusUpdateSerial,
3031
};
3132
use sanitize_filename::is_sanitized;
32-
use tokio::{fs, sync::RwLock};
33+
use tokio::fs;
34+
use tokio::sync::{watch, Mutex, RwLock};
3335
use walkdir::WalkDir;
3436
use yerpc::rpc;
3537

@@ -57,21 +59,45 @@ use self::types::{
5759
use crate::api::types::chat_list::{get_chat_list_item_by_id, ChatListItemFetchResult};
5860
use crate::api::types::qr::QrObject;
5961

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+
6080
#[derive(Clone, Debug)]
6181
pub struct CommandApi {
6282
pub(crate) accounts: Arc<RwLock<Accounts>>,
83+
84+
states: Arc<Mutex<BTreeMap<u32, AccountState>>>,
6385
}
6486

6587
impl CommandApi {
6688
pub fn new(accounts: Accounts) -> Self {
6789
CommandApi {
6890
accounts: Arc::new(RwLock::new(accounts)),
91+
states: Arc::new(Mutex::new(BTreeMap::new())),
6992
}
7093
}
7194

7295
#[allow(dead_code)]
7396
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+
}
75101
}
76102

77103
async fn get_context(&self, id: u32) -> Result<deltachat::context::Context> {
@@ -83,6 +109,38 @@ impl CommandApi {
83109
.ok_or_else(|| anyhow!("account with id {} not found", id))?;
84110
Ok(sc)
85111
}
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+
}
86144
}
87145

88146
#[rpc(all_positional, ts_outdir = "typescript/generated")]
@@ -115,7 +173,13 @@ impl CommandApi {
115173
}
116174

117175
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(())
119183
}
120184

121185
async fn get_all_account_ids(&self) -> Vec<u32> {
@@ -1358,31 +1422,35 @@ impl CommandApi {
13581422
///
13591423
/// This **stops IO** while it is running.
13601424
///
1361-
/// Returns once a remote device has retrieved the backup.
1425+
/// Returns once a remote device has retrieved the backup, or is cancelled.
13621426
async fn provide_backup(&self, account_id: u32) -> Result<()> {
13631427
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
13751442
}
13761443

13771444
/// Returns the text of the QR code for the running [`CommandApi::provide_backup`].
13781445
///
13791446
/// This QR code text can be used in [`CommandApi::get_backup`] on a second device to
13801447
/// 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.
13811452
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?;
13861454
qr::format_backup(&qr)
13871455
}
13881456

@@ -1391,12 +1459,14 @@ impl CommandApi {
13911459
/// This QR code can be used in [`CommandApi::get_backup`] on a second device to
13921460
/// retrieve the backup and setup this second device.
13931461
///
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+
///
13941466
/// Returns the QR code rendered as an SVG image.
13951467
async fn get_backup_qr_svg(&self, account_id: u32) -> Result<String> {
13961468
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?;
14001470
generate_backup_qr(&ctx, &qr).await
14011471
}
14021472

@@ -1900,3 +1970,15 @@ async fn get_config(
19001970
.await
19011971
}
19021972
}
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+
}

src/context.rs

Lines changed: 0 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ use crate::events::{Event, EventEmitter, EventType, Events};
2323
use crate::key::{DcKey, SignedPublicKey};
2424
use crate::login_param::LoginParam;
2525
use crate::message::{self, MessageState, MsgId};
26-
use crate::qr::Qr;
2726
use crate::quota::QuotaInfo;
2827
use crate::scheduler::SchedulerState;
2928
use crate::sql::Sql;
@@ -241,14 +240,6 @@ pub struct InnerContext {
241240

242241
/// If debug logging is enabled, this contains all necessary information
243242
pub(crate) debug_logging: RwLock<Option<DebugLogging>>,
244-
245-
/// QR code for currently running [`BackupProvider`].
246-
///
247-
/// This is only available if a backup export is currently running, it will also be
248-
/// holding the ongoing process while running.
249-
///
250-
/// [`BackupProvider`]: crate::imex::BackupProvider
251-
pub(crate) export_provider: std::sync::Mutex<Option<Qr>>,
252243
}
253244

254245
#[derive(Debug)]
@@ -393,7 +384,6 @@ impl Context {
393384
last_full_folder_scan: Mutex::new(None),
394385
last_error: std::sync::RwLock::new("".to_string()),
395386
debug_logging: RwLock::new(None),
396-
export_provider: std::sync::Mutex::new(None),
397387
};
398388

399389
let ctx = Context {
@@ -568,17 +558,6 @@ impl Context {
568558
}
569559
}
570560

571-
/// Returns the QR-code of the currently running [`BackupProvider`].
572-
///
573-
/// [`BackupProvider`]: crate::imex::BackupProvider
574-
pub fn backup_export_qr(&self) -> Option<Qr> {
575-
self.export_provider
576-
.lock()
577-
.expect("poisoned lock")
578-
.as_ref()
579-
.cloned()
580-
}
581-
582561
/*******************************************************************************
583562
* UI chat/message related API
584563
******************************************************************************/

src/imex/transfer.rs

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -126,16 +126,14 @@ impl BackupProvider {
126126
let handle = {
127127
let context = context.clone();
128128
tokio::spawn(async move {
129-
let res = Self::watch_provider(&context, provider, cancel_token, dbfile).await;
129+
let res = Self::watch_provider(&context, provider, cancel_token).await;
130130
context.free_ongoing().await;
131131
paused_guard.resume().await;
132+
drop(dbfile);
132133
res
133134
})
134135
};
135-
let slf = Self { handle, ticket };
136-
let qr = slf.qr();
137-
*context.export_provider.lock().expect("poisoned lock") = Some(qr);
138-
Ok(slf)
136+
Ok(Self { handle, ticket })
139137
}
140138

141139
/// Creates the provider task.
@@ -189,7 +187,6 @@ impl BackupProvider {
189187
context: &Context,
190188
mut provider: Provider,
191189
cancel_token: Receiver<()>,
192-
_dbfile: TempPathGuard,
193190
) -> Result<()> {
194191
// _dbfile exists so we can clean up the file once it is no longer needed
195192
let mut events = provider.subscribe();
@@ -255,11 +252,6 @@ impl BackupProvider {
255252
},
256253
}
257254
};
258-
context
259-
.export_provider
260-
.lock()
261-
.expect("poisoned lock")
262-
.take();
263255
match &res {
264256
Ok(_) => context.emit_event(SendProgress::Completed.into()),
265257
Err(err) => {

0 commit comments

Comments
 (0)