Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
aca0c77
refactor: move convert folder meaning logic in own method
Simon-Laux Dec 11, 2023
5bfe315
feat: add background fetch method
Simon-Laux Dec 11, 2023
935755c
api: jsonrpc: add `background_fetch_for_all_accounts`
Simon-Laux Dec 12, 2023
e113ef2
api: cffi: add `dc_accounts_background_fetch_with_timeout`
Simon-Laux Dec 12, 2023
002028e
Test server has no sentbox folder
Simon-Laux Dec 12, 2023
86fd139
fix iOS build issue
Simon-Laux Dec 12, 2023
ffe53c4
log time that the function took
Simon-Laux Dec 12, 2023
79119de
don't hold write lock in cffi (this blocked events)
Simon-Laux Dec 12, 2023
5c7e276
Apply suggestions from code review
Simon-Laux Dec 12, 2023
baef362
rename cffi function
Simon-Laux Dec 12, 2023
a6101b7
add rate limit for quota check in background fetch (12h for now)
Simon-Laux Jan 4, 2024
1cbe8ab
BackgroundFetchCompletedForAllAccounts event
Simon-Laux Jan 4, 2024
cc043ca
rename event and mention event in method documentation
Simon-Laux Jan 12, 2024
02090a8
cargo fmt
Simon-Laux Jan 12, 2024
72fda75
rename event also in core
Simon-Laux Jan 13, 2024
74e0631
fix: do not log error if watched folder is not configured
link2xt Jan 14, 2024
02f6b9d
Apply suggestions from code review
Simon-Laux Jan 14, 2024
ec92b02
Hide `background_fetch_without_timeout` from public API
link2xt Jan 29, 2024
a40d426
s/forgeting/forgetting/
link2xt Jan 30, 2024
fdba0b2
Emit DC_EVENT_ACCOUNTS_BACKGROUND_FETCH_DONE even on timeout
link2xt Jan 30, 2024
fa5c6ad
Make Accounts::background_fetch() not return Result
link2xt Jan 30, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions deltachat-ffi/deltachat.h
Original file line number Diff line number Diff line change
Expand Up @@ -3151,6 +3151,23 @@ void dc_accounts_maybe_network (dc_accounts_t* accounts);
void dc_accounts_maybe_network_lost (dc_accounts_t* accounts);


/**
* Perform a background fetch for all accounts in parallel with a timeout.
* Pauses the scheduler, fetches messages from imap and then resumes the scheduler.
*
* dc_accounts_background_fetch() was created for the iOS Background fetch.
*
* The `DC_EVENT_ACCOUNTS_BACKGROUND_FETCH_DONE` event is emitted at the end
* even in case of timeout, unless the function fails and returns 0.
* Process all events until you get this one and you can safely return to the background
* without forgetting to create notifications caused by timing race conditions.
*
* @memberof dc_accounts_t
* @param timeout The timeout in seconds
* @return Return 1 if DC_EVENT_ACCOUNTS_BACKGROUND_FETCH_DONE was emitted and 0 otherwise.
*/
int dc_accounts_background_fetch (dc_accounts_t* accounts, uint64_t timeout);

/**
* Create the event emitter that is used to receive events.
*
Expand Down Expand Up @@ -6255,6 +6272,16 @@ void dc_event_unref(dc_event_t* event);

#define DC_EVENT_WEBXDC_INSTANCE_DELETED 2121

/**
* Tells that the Background fetch was completed (or timed out).
*
* This event acts as a marker, when you reach this event you can be sure
* that all events emitted during the background fetch were processed.
*
* This event is only emitted by the account manager
*/

#define DC_EVENT_ACCOUNTS_BACKGROUND_FETCH_DONE 2200

/**
* @}
Expand Down
26 changes: 25 additions & 1 deletion deltachat-ffi/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -559,6 +559,7 @@ pub unsafe extern "C" fn dc_event_get_id(event: *mut dc_event_t) -> libc::c_int
EventType::ConfigSynced { .. } => 2111,
EventType::WebxdcStatusUpdate { .. } => 2120,
EventType::WebxdcInstanceDeleted { .. } => 2121,
EventType::AccountsBackgroundFetchDone => 2200,
}
}

Expand Down Expand Up @@ -586,7 +587,8 @@ pub unsafe extern "C" fn dc_event_get_data1_int(event: *mut dc_event_t) -> libc:
| EventType::SelfavatarChanged
| EventType::ConfigSynced { .. }
| EventType::IncomingMsgBunch { .. }
| EventType::ErrorSelfNotInGroup(_) => 0,
| EventType::ErrorSelfNotInGroup(_)
| EventType::AccountsBackgroundFetchDone => 0,
EventType::MsgsChanged { chat_id, .. }
| EventType::ReactionsChanged { chat_id, .. }
| EventType::IncomingMsg { chat_id, .. }
Expand Down Expand Up @@ -646,6 +648,7 @@ pub unsafe extern "C" fn dc_event_get_data2_int(event: *mut dc_event_t) -> libc:
| EventType::WebxdcInstanceDeleted { .. }
| EventType::IncomingMsgBunch { .. }
| EventType::SelfavatarChanged
| EventType::AccountsBackgroundFetchDone
| EventType::ConfigSynced { .. } => 0,
EventType::ChatModified(_) => 0,
EventType::MsgsChanged { msg_id, .. }
Expand Down Expand Up @@ -708,6 +711,7 @@ pub unsafe extern "C" fn dc_event_get_data2_str(event: *mut dc_event_t) -> *mut
| EventType::SelfavatarChanged
| EventType::WebxdcStatusUpdate { .. }
| EventType::WebxdcInstanceDeleted { .. }
| EventType::AccountsBackgroundFetchDone
| EventType::ChatEphemeralTimerModified { .. } => ptr::null_mut(),
EventType::ConfigureProgress { comment, .. } => {
if let Some(comment) = comment {
Expand Down Expand Up @@ -4898,6 +4902,26 @@ pub unsafe extern "C" fn dc_accounts_maybe_network_lost(accounts: *mut dc_accoun
block_on(async move { accounts.write().await.maybe_network_lost().await });
}

#[no_mangle]
pub unsafe extern "C" fn dc_accounts_background_fetch(
accounts: *mut dc_accounts_t,
timeout_in_seconds: u64,
) -> libc::c_int {
if accounts.is_null() || timeout_in_seconds <= 2 {
eprintln!("ignoring careless call to dc_accounts_background_fetch()");
return 0;
}

let accounts = &*accounts;
block_on(async move {
let accounts = accounts.read().await;
accounts
.background_fetch(Duration::from_secs(timeout_in_seconds))
.await;
});
1
}

#[no_mangle]
pub unsafe extern "C" fn dc_accounts_get_event_emitter(
accounts: *mut dc_accounts_t,
Expand Down
14 changes: 14 additions & 0 deletions deltachat-jsonrpc/src/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,20 @@ impl CommandApi {
Ok(())
}

/// Performs a background fetch for all accounts in parallel with a timeout.
///
/// The `AccountsBackgroundFetchDone` event is emitted at the end even in case of timeout.
/// Process all events until you get this one and you can safely return to the background
/// without forgetting to create notifications caused by timing race conditions.
async fn accounts_background_fetch(&self, timeout_in_seconds: f64) -> Result<()> {
self.accounts
.write()
.await
.background_fetch(std::time::Duration::from_secs_f64(timeout_in_seconds))
.await;
Ok(())
}

// ---------------------------------------------
// Methods that work on individual accounts
// ---------------------------------------------
Expand Down
8 changes: 8 additions & 0 deletions deltachat-jsonrpc/src/api/types/events.rs
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,13 @@ pub enum EventType {
/// Inform that a message containing a webxdc instance has been deleted
#[serde(rename_all = "camelCase")]
WebxdcInstanceDeleted { msg_id: u32 },

/// Tells that the Background fetch was completed (or timed out).
/// This event acts as a marker, when you reach this event you can be sure
/// that all events emitted during the background fetch were processed.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice explanation!

///
/// This event is only emitted by the account manager
AccountsBackgroundFetchDone,
}

impl From<CoreEventType> for EventType {
Expand Down Expand Up @@ -353,6 +360,7 @@ impl From<CoreEventType> for EventType {
CoreEventType::WebxdcInstanceDeleted { msg_id } => WebxdcInstanceDeleted {
msg_id: msg_id.to_u32(),
},
CoreEventType::AccountsBackgroundFetchDone => AccountsBackgroundFetchDone,
}
}
}
1 change: 1 addition & 0 deletions node/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ module.exports = {
DC_DOWNLOAD_FAILURE: 20,
DC_DOWNLOAD_IN_PROGRESS: 1000,
DC_DOWNLOAD_UNDECIPHERABLE: 30,
DC_EVENT_ACCOUNTS_BACKGROUND_FETCH_DONE: 2200,
DC_EVENT_CHAT_EPHEMERAL_TIMER_MODIFIED: 2021,
DC_EVENT_CHAT_MODIFIED: 2020,
DC_EVENT_CONFIGURE_PROGRESS: 2041,
Expand Down
3 changes: 2 additions & 1 deletion node/events.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,5 +36,6 @@ module.exports = {
2110: 'DC_EVENT_SELFAVATAR_CHANGED',
2111: 'DC_EVENT_CONFIG_SYNCED',
2120: 'DC_EVENT_WEBXDC_STATUS_UPDATE',
2121: 'DC_EVENT_WEBXDC_INSTANCE_DELETED'
2121: 'DC_EVENT_WEBXDC_INSTANCE_DELETED',
2200: 'DC_EVENT_ACCOUNTS_BACKGROUND_FETCH_DONE'
}
2 changes: 2 additions & 0 deletions node/lib/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export enum C {
DC_DOWNLOAD_FAILURE = 20,
DC_DOWNLOAD_IN_PROGRESS = 1000,
DC_DOWNLOAD_UNDECIPHERABLE = 30,
DC_EVENT_ACCOUNTS_BACKGROUND_FETCH_DONE = 2200,
DC_EVENT_CHAT_EPHEMERAL_TIMER_MODIFIED = 2021,
DC_EVENT_CHAT_MODIFIED = 2020,
DC_EVENT_CONFIGURE_PROGRESS = 2041,
Expand Down Expand Up @@ -326,4 +327,5 @@ export const EventId2EventName: { [key: number]: string } = {
2111: 'DC_EVENT_CONFIG_SYNCED',
2120: 'DC_EVENT_WEBXDC_STATUS_UPDATE',
2121: 'DC_EVENT_WEBXDC_INSTANCE_DELETED',
2200: 'DC_EVENT_ACCOUNTS_BACKGROUND_FETCH_DONE',
}
37 changes: 37 additions & 0 deletions src/accounts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use std::future::Future;
use std::path::{Path, PathBuf};

use anyhow::{ensure, Context as _, Result};
use futures::future::join_all;
use serde::{Deserialize, Serialize};
use tokio::fs;
use tokio::io::AsyncWriteExt;
Expand Down Expand Up @@ -291,6 +292,42 @@ impl Accounts {
}
}

/// Performs a background fetch for all accounts in parallel.
///
/// This is an auxiliary function and not part of public API.
/// Use [Accounts::background_fetch] instead.
async fn background_fetch_without_timeout(&self) {
async fn background_fetch_and_log_error(account: Context) {
if let Err(error) = account.background_fetch().await {
warn!(account, "{error:#}");
}
}

join_all(
self.accounts
.values()
.cloned()
.map(background_fetch_and_log_error),
)
.await;
}

/// Performs a background fetch for all accounts in parallel with a timeout.
///
/// The `AccountsBackgroundFetchDone` event is emitted at the end,
/// process all events until you get this one and you can safely return to the background
/// without forgetting to create notifications caused by timing race conditions.
pub async fn background_fetch(&self, timeout: std::time::Duration) {
if let Err(_err) =
tokio::time::timeout(timeout, self.background_fetch_without_timeout()).await
{
self.emit_event(EventType::Warning(
"Background fetch timed out.".to_string(),
));
}
self.emit_event(EventType::AccountsBackgroundFetchDone);
}

/// Emits a single event.
pub fn emit_event(&self, event: EventType) {
self.events.emit(Event { id: 0, typ: event })
Expand Down
3 changes: 3 additions & 0 deletions src/constants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,9 @@ pub(crate) const DC_FOLDERS_CONFIGURED_VERSION: i32 = 4;
// `max_smtp_rcpt_to` in the provider db.
pub(crate) const DEFAULT_MAX_SMTP_RCPT_TO: usize = 50;

/// How far the last quota check needs to be in the past to be checked by the background function (in seconds).
pub(crate) const DC_BACKGROUND_FETCH_QUOTA_CHECK_RATELIMIT: i64 = 12 * 60 * 60; // 12 hours

#[cfg(test)]
mod tests {
use num_traits::FromPrimitive;
Expand Down
55 changes: 52 additions & 3 deletions src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,16 @@ use tokio::sync::{Mutex, Notify, RwLock};

use crate::chat::{get_chat_cnt, ChatId};
use crate::config::Config;
use crate::constants::DC_VERSION_STR;
use crate::constants::{DC_BACKGROUND_FETCH_QUOTA_CHECK_RATELIMIT, DC_VERSION_STR};
use crate::contact::Contact;
use crate::debug_logging::DebugLogging;
use crate::events::{Event, EventEmitter, EventType, Events};
use crate::imap::ServerMetadata;
use crate::imap::{FolderMeaning, Imap, ServerMetadata};
use crate::key::{load_self_public_key, DcKey as _};
use crate::login_param::LoginParam;
use crate::message::{self, MessageState, MsgId};
use crate::quota::QuotaInfo;
use crate::scheduler::SchedulerState;
use crate::scheduler::{convert_folder_meaning, SchedulerState};
use crate::sql::Sql;
use crate::stock_str::StockStrings;
use crate::timesmearing::SmearedTimestamp;
Expand Down Expand Up @@ -441,6 +441,55 @@ impl Context {
self.scheduler.maybe_network().await;
}

/// Does a background fetch
/// pauses the scheduler and does one imap fetch, then unpauses and returns
pub async fn background_fetch(&self) -> Result<()> {
if !(self.is_configured().await?) {
return Ok(());
}

let address = self.get_primary_self_addr().await?;
let time_start = std::time::SystemTime::now();
info!(self, "background_fetch started fetching {address}");

let _pause_guard = self.scheduler.pause(self.clone()).await?;

// connection
let mut connection = Imap::new_configured(self, channel::bounded(1).1).await?;
connection.prepare(self).await?;

// fetch imap folders
for folder_meaning in [FolderMeaning::Inbox, FolderMeaning::Mvbox] {
let (_, watch_folder) = convert_folder_meaning(self, folder_meaning).await?;
connection
.fetch_move_delete(self, &watch_folder, folder_meaning)
.await?;
}

// update quota (to send warning if full) - but only check it once in a while
let quota_needs_update = {
let quota = self.quota.read().await;
quota
.as_ref()
.filter(|quota| quota.modified + DC_BACKGROUND_FETCH_QUOTA_CHECK_RATELIMIT > time())
.is_none()
};

if quota_needs_update {
if let Err(err) = self.update_recent_quota(&mut connection).await {
warn!(self, "Failed to update quota: {err:#}.");
}
}

info!(
self,
"background_fetch done for {address} took {:?}",
time_start.elapsed().unwrap_or_default()
);

Ok(())
}

pub(crate) async fn schedule_resync(&self) -> Result<()> {
self.resync_request.store(true, Ordering::Relaxed);
self.scheduler.interrupt_inbox().await;
Expand Down
7 changes: 7 additions & 0 deletions src/events/payload.rs
Original file line number Diff line number Diff line change
Expand Up @@ -287,4 +287,11 @@ pub enum EventType {
/// ID of the deleted message.
msg_id: MsgId,
},

/// Tells that the Background fetch was completed (or timed out).
/// This event acts as a marker, when you reach this event you can be sure
/// that all events emitted during the background fetch were processed.
///
/// This event is only emitted by the account manager
AccountsBackgroundFetchDone,
}
Loading