mirror of
https://github.com/chatmail/core.git
synced 2026-10-02 11:10:25 +03:00
Add init_transports() API
This commit is contained in:
@@ -525,6 +525,18 @@ impl CommandApi {
|
|||||||
ctx.add_transport_from_qr(&qr).await
|
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.
|
/// 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
|
/// Use [Self::add_or_update_transport()] to add or change a transport
|
||||||
/// and [Self::delete_transport()] to remove a transport.
|
/// and [Self::delete_transport()] to remove a transport.
|
||||||
|
|||||||
+100
-10
@@ -6,19 +6,25 @@
|
|||||||
//! which migrations seed with a list of known chatmail relays.
|
//! which migrations seed with a list of known chatmail relays.
|
||||||
//!
|
//!
|
||||||
//! Status of implementation:
|
//! Status of implementation:
|
||||||
//! Additions are attempted right before going into IMAP IDLE,
|
//!
|
||||||
//! i.e. only while connected and with nothing more important to do,
|
//! - When the UI uses `init_transports()`, the user gets 3 randomly selected relays.
|
||||||
//! and only if a UI opted in via [`Config::Autorelay`].
|
//!
|
||||||
//! Once a profile has reached `NUM_TRANSPORTS_TARGET` transports,
|
//! - Later additions are attempted right before going into IMAP IDLE,
|
||||||
//! [`Config::AutorelayFinished`] is set and nothing is ever added again,
|
//! i.e. only while connected and with nothing more important to do,
|
||||||
//! so deleting a transport later does not pull in a replacement.
|
//! 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 std::pin::Pin;
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use deltachat_contact_tools::addr_normalize;
|
use deltachat_contact_tools::{EmailAddress, addr_normalize};
|
||||||
use rand::distr::{Alphanumeric, SampleString};
|
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::config::{self, Config};
|
||||||
use crate::log::{LogExt, warn};
|
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:
|
/// 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 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(
|
pub(crate) fn maybe_add_additional_relays(
|
||||||
context: Context,
|
context: Context,
|
||||||
) -> Pin<Box<dyn Future<Output = ()> + Send>> {
|
) -> 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> {
|
async fn maybe_add_additional_relays_inner(context: &Context, skip_network: bool) -> Result<bool> {
|
||||||
let now = time();
|
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.
|
// Housekeeping or automatic relay management is already running in another thread, do nothing.
|
||||||
return Ok(false);
|
return Ok(false);
|
||||||
};
|
};
|
||||||
@@ -134,7 +224,7 @@ async fn maybe_add_additional_relays_inner(context: &Context, skip_network: bool
|
|||||||
Ok(relay_added)
|
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 cutoff_timestamp = now.saturating_sub(BACKOFF_PERIOD_FOR_NOT_WORKING_RELAY);
|
||||||
let candidates: Vec<String> = context
|
let candidates: Vec<String> = context
|
||||||
.sql
|
.sql
|
||||||
|
|||||||
@@ -97,7 +97,7 @@ async fn test_maybe_add_additional_relays_mutex_held() -> Result<()> {
|
|||||||
|
|
||||||
// Hold the housekeeping mutex ourselves, simulating another task
|
// Hold the housekeeping mutex ourselves, simulating another task
|
||||||
// already running housekeeping or relay management.
|
// 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;
|
assert_autorelay_does_nothing(t).await;
|
||||||
|
|
||||||
|
|||||||
+35
-1
@@ -40,7 +40,7 @@ use crate::transport::{
|
|||||||
ConnectionCandidate, delete_transport_row, maybe_update_sending_transport,
|
ConnectionCandidate, delete_transport_row, maybe_update_sending_transport,
|
||||||
purge_transport_caches, send_sync_transports, transport_addrs,
|
purge_transport_caches, send_sync_transports, transport_addrs,
|
||||||
};
|
};
|
||||||
use crate::{EventType, stock_str};
|
use crate::{EventType, autorelay, stock_str};
|
||||||
|
|
||||||
/// Maximum number of relays.
|
/// Maximum number of relays.
|
||||||
///
|
///
|
||||||
@@ -190,6 +190,40 @@ impl Context {
|
|||||||
Ok(())
|
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.
|
/// 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
|
/// Use [Self::add_or_update_transport()] to add or change a transport
|
||||||
/// and [Self::delete_transport()] to delete a transport.
|
/// and [Self::delete_transport()] to delete a transport.
|
||||||
|
|||||||
+3
-3
@@ -231,8 +231,8 @@ pub struct InnerContext {
|
|||||||
/// This is a global mutex-like state for operations which should be modal in the
|
/// This is a global mutex-like state for operations which should be modal in the
|
||||||
/// clients.
|
/// clients.
|
||||||
running_state: RwLock<RunningState>,
|
running_state: RwLock<RunningState>,
|
||||||
/// Mutex to prevent running housekeeping or relay management from multiple threads at once.
|
/// Lock to prevent running housekeeping or relay management from multiple threads at once.
|
||||||
pub(crate) background_task_mutex: Mutex<()>,
|
pub(crate) background_task_lock: RwLock<()>,
|
||||||
|
|
||||||
/// Mutex to prevent multiple IMAP loops from fetching the messages at once.
|
/// Mutex to prevent multiple IMAP loops from fetching the messages at once.
|
||||||
///
|
///
|
||||||
@@ -487,7 +487,7 @@ impl Context {
|
|||||||
blobdir,
|
blobdir,
|
||||||
running_state: RwLock::new(Default::default()),
|
running_state: RwLock::new(Default::default()),
|
||||||
sql: Sql::new(dbfile),
|
sql: Sql::new(dbfile),
|
||||||
background_task_mutex: Mutex::new(()),
|
background_task_lock: RwLock::new(()),
|
||||||
fetch_msgs_mutex: Mutex::new(()),
|
fetch_msgs_mutex: Mutex::new(()),
|
||||||
translated_stockstrings: stockstrings,
|
translated_stockstrings: stockstrings,
|
||||||
events,
|
events,
|
||||||
|
|||||||
+1
-1
@@ -782,7 +782,7 @@ async fn incremental_vacuum(context: &Context) -> Result<()> {
|
|||||||
|
|
||||||
/// Cleanup the account to restore some storage and optimize the database.
|
/// Cleanup the account to restore some storage and optimize the database.
|
||||||
pub async fn housekeeping(context: &Context) -> Result<()> {
|
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.
|
// Housekeeping is already running in another thread, do nothing.
|
||||||
return Ok(());
|
return Ok(());
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user