mirror of
https://github.com/chatmail/core.git
synced 2026-09-22 04:58:47 +03:00
Add init_transports() API
This commit is contained in:
@@ -525,6 +525,18 @@ impl CommandApi {
|
||||
ctx.add_transport_from_qr(&qr).await
|
||||
}
|
||||
|
||||
/// Automatically adds up to three transports.
|
||||
///
|
||||
/// If the user just scanned a QR code of type `Account`, `Login`,
|
||||
/// `AskVerifyContact`, `AskVerifyGroup`, or `AskJoinBroadcast`,
|
||||
/// then UI implementations should pass it as the `qr` parameter.
|
||||
/// The host(s) from the QR code will then also be considered
|
||||
/// for creating an account there.
|
||||
async fn init_transports(&self, account_id: u32, qr: Option<String>) -> Result<()> {
|
||||
let ctx = self.get_context(account_id).await?;
|
||||
ctx.init_transports(qr.as_deref()).await
|
||||
}
|
||||
|
||||
/// Returns the list of all email accounts that are used as a transport in the current profile.
|
||||
/// Use [Self::add_or_update_transport()] to add or change a transport
|
||||
/// and [Self::delete_transport()] to remove a transport.
|
||||
|
||||
110
src/autorelay.rs
110
src/autorelay.rs
@@ -6,19 +6,25 @@
|
||||
//! which migrations seed with a list of known chatmail relays.
|
||||
//!
|
||||
//! Status of implementation:
|
||||
//! Additions are attempted right before going into IMAP IDLE,
|
||||
//! i.e. only while connected and with nothing more important to do,
|
||||
//! and only if a UI opted in via [`Config::Autorelay`].
|
||||
//! Once a profile has reached `NUM_TRANSPORTS_TARGET` transports,
|
||||
//! [`Config::AutorelayFinished`] is set and nothing is ever added again,
|
||||
//! so deleting a transport later does not pull in a replacement.
|
||||
//!
|
||||
//! - When the UI uses `init_transports()`, the user gets 3 randomly selected relays.
|
||||
//!
|
||||
//! - Later additions are attempted right before going into IMAP IDLE,
|
||||
//! i.e. only while connected and with nothing more important to do,
|
||||
//! and only if a UI opted in via [`Config::Autorelay`].
|
||||
//! Once a profile has reached `NUM_TRANSPORTS_TARGET` transports,
|
||||
//! [`Config::AutorelayFinished`] is set and nothing is ever added again,
|
||||
//! so deleting a transport later does not pull in a replacement.
|
||||
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
use std::pin::Pin;
|
||||
|
||||
use anyhow::Result;
|
||||
use deltachat_contact_tools::addr_normalize;
|
||||
use deltachat_contact_tools::{EmailAddress, addr_normalize};
|
||||
use rand::distr::{Alphanumeric, SampleString};
|
||||
use rand::seq::IndexedRandom;
|
||||
use rand::rng;
|
||||
use rand::seq::{IndexedRandom, SliceRandom as _};
|
||||
use tokio::task::JoinSet;
|
||||
|
||||
use crate::config::{self, Config};
|
||||
use crate::log::{LogExt, warn};
|
||||
@@ -32,6 +38,90 @@ const AUTOMATIC_ADDITION_DEBOUNCE_SECONDS: i64 = 60 * 60; // one hour
|
||||
/// How long we ignore a relay candidate after failing to connect to it:
|
||||
const BACKOFF_PERIOD_FOR_NOT_WORKING_RELAY: i64 = 60 * 60 * 24 * 7; // one week
|
||||
|
||||
const DEFAULT_RELAY_CANDIDATES: &[&str] = &[
|
||||
"chat.adminforge.de",
|
||||
"tarpit.fun",
|
||||
"sweetfern.net",
|
||||
"chat.nuvon.app",
|
||||
"nchrcht.la10cy.net",
|
||||
"chat.sus.fr",
|
||||
"chat.tinydispatch.org",
|
||||
"chtml.ca",
|
||||
"chatmail.uk",
|
||||
];
|
||||
|
||||
pub(crate) async fn init_transports_inner(
|
||||
context: &Context,
|
||||
addrs_from_qr: Vec<String>,
|
||||
) -> Result<(), anyhow::Error> {
|
||||
// TODO The default relay candidates need to be updated in the database, too.
|
||||
// It would be annoying to have to write a migration everytime a relay candidate comes or goes;
|
||||
// The solution is to make the relay candidates list into a const,
|
||||
// and check in `load_relay_candidates()` whether any of them should be added
|
||||
// rather than in a migration
|
||||
// (if we need to remove some later, we will then need another const `REMOVED_RELAY_CANDIDATES` which are ignored;
|
||||
// or alternatively we could use `relay_candidates` table only for saving the last used timestamps,
|
||||
// and if we later want to add other sources for relay candidates then we need another table for that)
|
||||
let mut candidates: Vec<&str> = DEFAULT_RELAY_CANDIDATES.into();
|
||||
candidates.shuffle(&mut rng());
|
||||
|
||||
let (relays_sender, relays_receiver) = async_channel::unbounded::<String>();
|
||||
for addr in addrs_from_qr {
|
||||
let email = EmailAddress::new(&addr)?;
|
||||
relays_sender.try_send(email.domain)?;
|
||||
}
|
||||
for relay in candidates {
|
||||
relays_sender.try_send(relay.to_string())?;
|
||||
}
|
||||
|
||||
let mut join_set = JoinSet::new();
|
||||
for _ in 0..NUM_TRANSPORTS_TARGET {
|
||||
let context = context.clone();
|
||||
let relays_receiver = relays_receiver.clone();
|
||||
join_set.spawn(async move {
|
||||
loop {
|
||||
// Take a lock in order to prevent other relay management code
|
||||
// from running simultaneously
|
||||
let _lock = context.background_task_lock.read();
|
||||
|
||||
let Ok(host) = relays_receiver.try_recv() else {
|
||||
return false; // No more relays to try
|
||||
};
|
||||
let param = login_param_from_host(&host);
|
||||
let skip_network = false;
|
||||
let res = crate::configure::configure(&context, ¶m, skip_network).await;
|
||||
if let Err(err) = res {
|
||||
warn!(context, "Failed to init transport {host}: {err:#}.");
|
||||
// Try another relay in the next iteration of the loop
|
||||
} else {
|
||||
if context.count_transports().await.unwrap_or(0) >= NUM_TRANSPORTS_TARGET {
|
||||
context
|
||||
.set_config_bool(Config::AutorelayFinished, true)
|
||||
.await
|
||||
.log_err(&context)
|
||||
.ok();
|
||||
}
|
||||
return true; // Success
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
while let Some(success) = join_set.join_next().await {
|
||||
if success? {
|
||||
break;
|
||||
}
|
||||
// If this task was not successful, continue waiting for the other tasks
|
||||
// because maybe one of the ongoing configuration attempts will be successful
|
||||
}
|
||||
|
||||
join_set.detach_all();
|
||||
|
||||
context.set_config_bool(Config::Autorelay, true).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn maybe_add_additional_relays(
|
||||
context: Context,
|
||||
) -> Pin<Box<dyn Future<Output = ()> + Send>> {
|
||||
@@ -57,7 +147,7 @@ pub(crate) fn maybe_add_additional_relays(
|
||||
async fn maybe_add_additional_relays_inner(context: &Context, skip_network: bool) -> Result<bool> {
|
||||
let now = time();
|
||||
|
||||
let Ok(_lock) = context.background_task_mutex.try_lock() else {
|
||||
let Ok(_lock) = context.background_task_lock.try_write() else {
|
||||
// Housekeeping or automatic relay management is already running in another thread, do nothing.
|
||||
return Ok(false);
|
||||
};
|
||||
@@ -134,7 +224,7 @@ async fn maybe_add_additional_relays_inner(context: &Context, skip_network: bool
|
||||
Ok(relay_added)
|
||||
}
|
||||
|
||||
async fn load_relay_candidates(context: &Context, now: i64) -> Result<Vec<String>, anyhow::Error> {
|
||||
pub(crate) async fn load_relay_candidates(context: &Context, now: i64) -> Result<Vec<String>> {
|
||||
let cutoff_timestamp = now.saturating_sub(BACKOFF_PERIOD_FOR_NOT_WORKING_RELAY);
|
||||
let candidates: Vec<String> = context
|
||||
.sql
|
||||
|
||||
@@ -97,7 +97,7 @@ async fn test_maybe_add_additional_relays_mutex_held() -> Result<()> {
|
||||
|
||||
// Hold the housekeeping mutex ourselves, simulating another task
|
||||
// already running housekeeping or relay management.
|
||||
let _lock = t.background_task_mutex.lock().await;
|
||||
let _lock = t.background_task_lock.write().await;
|
||||
|
||||
assert_autorelay_does_nothing(t).await;
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@ use crate::transport::{
|
||||
ConnectionCandidate, delete_transport_row, maybe_update_sending_transport,
|
||||
purge_transport_caches, send_sync_transports, transport_addrs,
|
||||
};
|
||||
use crate::{EventType, stock_str};
|
||||
use crate::{EventType, autorelay, stock_str};
|
||||
|
||||
/// Maximum number of relays.
|
||||
///
|
||||
@@ -190,6 +190,40 @@ impl Context {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Automatically adds up to three transports.
|
||||
///
|
||||
/// If the user just scanned a QR code of type `Account`, `Login`,
|
||||
/// `AskVerifyContact`, `AskVerifyGroup`, or `AskJoinBroadcast`,
|
||||
/// then UI implementations should pass it as the `qr` parameter.
|
||||
/// The host(s) from the QR code will then also be considered
|
||||
/// for creating an account there.
|
||||
pub async fn init_transports(&self, qr: Option<&str>) -> Result<()> {
|
||||
if self.is_configured().await? {
|
||||
bail!("Transports are already initialized");
|
||||
}
|
||||
self.stop_io().await;
|
||||
|
||||
let mut addrs_from_qr = vec![];
|
||||
|
||||
if let Some(qr) = qr {
|
||||
match crate::qr::check_qr(self, qr).await? {
|
||||
crate::qr::Qr::Account { .. } | crate::qr::Qr::Login { .. } => {
|
||||
return self.add_transport_from_qr(qr).await;
|
||||
}
|
||||
crate::qr::Qr::AskVerifyContact { addrs, .. }
|
||||
| crate::qr::Qr::AskVerifyGroup { addrs, .. }
|
||||
| crate::qr::Qr::AskJoinBroadcast { addrs, .. } => addrs_from_qr = addrs,
|
||||
_ => bail!("This QR code can't be used to initialize transports"),
|
||||
}
|
||||
}
|
||||
|
||||
autorelay::init_transports_inner(self, addrs_from_qr).await?;
|
||||
|
||||
self.start_io().await;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Returns the list of all email accounts that are used as a transport in the current profile.
|
||||
/// Use [Self::add_or_update_transport()] to add or change a transport
|
||||
/// and [Self::delete_transport()] to delete a transport.
|
||||
|
||||
@@ -231,8 +231,8 @@ pub struct InnerContext {
|
||||
/// This is a global mutex-like state for operations which should be modal in the
|
||||
/// clients.
|
||||
running_state: RwLock<RunningState>,
|
||||
/// Mutex to prevent running housekeeping or relay management from multiple threads at once.
|
||||
pub(crate) background_task_mutex: Mutex<()>,
|
||||
/// Lock to prevent running housekeeping or relay management from multiple threads at once.
|
||||
pub(crate) background_task_lock: RwLock<()>,
|
||||
|
||||
/// Mutex to prevent multiple IMAP loops from fetching the messages at once.
|
||||
///
|
||||
@@ -487,7 +487,7 @@ impl Context {
|
||||
blobdir,
|
||||
running_state: RwLock::new(Default::default()),
|
||||
sql: Sql::new(dbfile),
|
||||
background_task_mutex: Mutex::new(()),
|
||||
background_task_lock: RwLock::new(()),
|
||||
fetch_msgs_mutex: Mutex::new(()),
|
||||
translated_stockstrings: stockstrings,
|
||||
events,
|
||||
|
||||
@@ -782,7 +782,7 @@ async fn incremental_vacuum(context: &Context) -> Result<()> {
|
||||
|
||||
/// Cleanup the account to restore some storage and optimize the database.
|
||||
pub async fn housekeeping(context: &Context) -> Result<()> {
|
||||
let Ok(_housekeeping_lock) = context.background_task_mutex.try_lock() else {
|
||||
let Ok(_housekeeping_lock) = context.background_task_lock.try_write() else {
|
||||
// Housekeeping is already running in another thread, do nothing.
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user