mirror of
https://github.com/chatmail/core.git
synced 2026-05-20 15:26:30 +03:00
fix(deltachat-jsonrpc): block in inner_get_backup_qr
This change avoids the race between `provide_backup` changing the state from NoProvider to Pending and a call to `get_backup_qr` or `get_backup_qr_svg`. With this change `get_backup_qr` and `get_backup_qr_svg` always block until QR code is available, even if `provide_backup` was not called yet.
This commit is contained in:
@@ -1,5 +1,6 @@
|
|||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
use std::time::Duration;
|
||||||
use std::{collections::HashMap, str::FromStr};
|
use std::{collections::HashMap, str::FromStr};
|
||||||
|
|
||||||
use anyhow::{anyhow, bail, ensure, Context, Result};
|
use anyhow::{anyhow, bail, ensure, Context, Result};
|
||||||
@@ -62,14 +63,14 @@ use crate::api::types::qr::QrObject;
|
|||||||
struct AccountState {
|
struct AccountState {
|
||||||
/// The Qr code for current [`CommandApi::provide_backup`] call.
|
/// The Qr code for current [`CommandApi::provide_backup`] call.
|
||||||
///
|
///
|
||||||
/// If there currently is a call to [`CommandApi::provide_backup`] this will be
|
/// If there is currently is a call to [`CommandApi::provide_backup`] this will be
|
||||||
/// `Pending` or `Ready`, otherwise `NoProvider`.
|
/// `Some`, otherwise `None`.
|
||||||
backup_provider_qr: watch::Sender<ProviderQr>,
|
backup_provider_qr: watch::Sender<Option<Qr>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for AccountState {
|
impl Default for AccountState {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
let (tx, _rx) = watch::channel(ProviderQr::NoProvider);
|
let tx = watch::Sender::new(None);
|
||||||
Self {
|
Self {
|
||||||
backup_provider_qr: tx,
|
backup_provider_qr: tx,
|
||||||
}
|
}
|
||||||
@@ -123,21 +124,13 @@ impl CommandApi {
|
|||||||
.with_state(account_id, |state| state.backup_provider_qr.subscribe())
|
.with_state(account_id, |state| state.backup_provider_qr.subscribe())
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
let val: ProviderQr = receiver.borrow_and_update().clone();
|
loop {
|
||||||
match val {
|
if let Some(qr) = receiver.borrow_and_update().clone() {
|
||||||
ProviderQr::NoProvider => bail!("No backup being provided"),
|
return Ok(qr);
|
||||||
ProviderQr::Pending => loop {
|
}
|
||||||
if receiver.changed().await.is_err() {
|
if receiver.changed().await.is_err() {
|
||||||
bail!("No backup being provided (account state dropped)");
|
bail!("No backup being provided (account state dropped)");
|
||||||
}
|
}
|
||||||
let val: ProviderQr = receiver.borrow().clone();
|
|
||||||
match val {
|
|
||||||
ProviderQr::NoProvider => bail!("No backup being provided"),
|
|
||||||
ProviderQr::Pending => continue,
|
|
||||||
ProviderQr::Ready(qr) => break Ok(qr),
|
|
||||||
};
|
|
||||||
},
|
|
||||||
ProviderQr::Ready(qr) => Ok(qr),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1569,20 +1562,21 @@ impl CommandApi {
|
|||||||
/// Returns once a remote device has retrieved the backup, or is cancelled.
|
/// Returns once a remote device has retrieved the backup, or is cancelled.
|
||||||
async fn provide_backup(&self, account_id: u32) -> Result<()> {
|
async fn provide_backup(&self, account_id: u32) -> Result<()> {
|
||||||
let ctx = self.get_context(account_id).await?;
|
let ctx = self.get_context(account_id).await?;
|
||||||
self.with_state(account_id, |state| {
|
|
||||||
state.backup_provider_qr.send_replace(ProviderQr::Pending);
|
|
||||||
})
|
|
||||||
.await;
|
|
||||||
|
|
||||||
let provider = imex::BackupProvider::prepare(&ctx).await?;
|
let provider = imex::BackupProvider::prepare(&ctx).await?;
|
||||||
self.with_state(account_id, |state| {
|
self.with_state(account_id, |state| {
|
||||||
state
|
state.backup_provider_qr.send_replace(Some(provider.qr()));
|
||||||
.backup_provider_qr
|
|
||||||
.send_replace(ProviderQr::Ready(provider.qr()));
|
|
||||||
})
|
})
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
provider.await
|
let res = provider.await;
|
||||||
|
|
||||||
|
self.with_state(account_id, |state| {
|
||||||
|
state.backup_provider_qr.send_replace(None);
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
|
||||||
|
res
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the text of the QR code for the running [`CommandApi::provide_backup`].
|
/// Returns the text of the QR code for the running [`CommandApi::provide_backup`].
|
||||||
@@ -1590,11 +1584,17 @@ impl CommandApi {
|
|||||||
/// This QR code text can be used in [`CommandApi::get_backup`] on a second device to
|
/// This QR code text can be used in [`CommandApi::get_backup`] on a second device to
|
||||||
/// retrieve the backup and setup this second device.
|
/// retrieve the backup and setup this second device.
|
||||||
///
|
///
|
||||||
/// This call will fail if there is currently no concurrent call to
|
/// This call will block until the QR code is ready,
|
||||||
/// [`CommandApi::provide_backup`]. This call may block if the QR code is not yet
|
/// even if there is no concurrent call to [`CommandApi::provide_backup`],
|
||||||
/// ready.
|
/// but will fail after 10 seconds to avoid deadlocks.
|
||||||
async fn get_backup_qr(&self, account_id: u32) -> Result<String> {
|
async fn get_backup_qr(&self, account_id: u32) -> Result<String> {
|
||||||
let qr = self.inner_get_backup_qr(account_id).await?;
|
let qr = tokio::time::timeout(
|
||||||
|
Duration::from_secs(10),
|
||||||
|
self.inner_get_backup_qr(account_id),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.context("Backup provider did not start in time")?
|
||||||
|
.context("Failed to get backup QR code")?;
|
||||||
qr::format_backup(&qr)
|
qr::format_backup(&qr)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1603,14 +1603,20 @@ impl CommandApi {
|
|||||||
/// This QR code can be used in [`CommandApi::get_backup`] on a second device to
|
/// This QR code can be used in [`CommandApi::get_backup`] on a second device to
|
||||||
/// retrieve the backup and setup this second device.
|
/// retrieve the backup and setup this second device.
|
||||||
///
|
///
|
||||||
/// This call will fail if there is currently no concurrent call to
|
/// This call will block until the QR code is ready,
|
||||||
/// [`CommandApi::provide_backup`]. This call may block if the QR code is not yet
|
/// even if there is no concurrent call to [`CommandApi::provide_backup`],
|
||||||
/// ready.
|
/// but will fail after 10 seconds to avoid deadlocks.
|
||||||
///
|
///
|
||||||
/// Returns the QR code rendered as an SVG image.
|
/// Returns the QR code rendered as an SVG image.
|
||||||
async fn get_backup_qr_svg(&self, account_id: u32) -> Result<String> {
|
async fn get_backup_qr_svg(&self, account_id: u32) -> Result<String> {
|
||||||
let ctx = self.get_context(account_id).await?;
|
let ctx = self.get_context(account_id).await?;
|
||||||
let qr = self.inner_get_backup_qr(account_id).await?;
|
let qr = tokio::time::timeout(
|
||||||
|
Duration::from_secs(10),
|
||||||
|
self.inner_get_backup_qr(account_id),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.context("Backup provider did not start in time")?
|
||||||
|
.context("Failed to get backup QR code")?;
|
||||||
generate_backup_qr(&ctx, &qr).await
|
generate_backup_qr(&ctx, &qr).await
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2141,15 +2147,3 @@ async fn get_config(
|
|||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Whether a QR code for a BackupProvider is currently available.
|
|
||||||
#[allow(clippy::large_enum_variant)]
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
enum ProviderQr {
|
|
||||||
/// There is no provider, asking for a QR is an error.
|
|
||||||
NoProvider,
|
|
||||||
/// There is a provider, the QR code is pending.
|
|
||||||
Pending,
|
|
||||||
/// There is a provider and QR code.
|
|
||||||
Ready(Qr),
|
|
||||||
}
|
|
||||||
|
|||||||
Reference in New Issue
Block a user