diff --git a/deltachat-ffi/deltachat.h b/deltachat-ffi/deltachat.h index b540cc5339..900ee80cce 100644 --- a/deltachat-ffi/deltachat.h +++ b/deltachat-ffi/deltachat.h @@ -22,6 +22,7 @@ typedef struct _dc_lot dc_lot_t; typedef struct _dc_provider dc_provider_t; typedef struct _dc_event dc_event_t; typedef struct _dc_event_emitter dc_event_emitter_t; +typedef struct _dc_event_channel dc_event_channel_t; typedef struct _dc_jsonrpc_instance dc_jsonrpc_instance_t; typedef struct _dc_backup_provider dc_backup_provider_t; @@ -3114,6 +3115,35 @@ int dc_receive_backup (dc_context_t* context, const char* qr); */ dc_accounts_t* dc_accounts_new (const char* dir, int writable); +/** + * Create a new account manager with an existing events channel, + * which allows you see events emitted during startup. + * + * The account manager takes an directory + * where all context-databases are placed in. + * To add a context to the account manager, + * use dc_accounts_add_account() or dc_accounts_migrate_account(). + * All account information are persisted. + * To remove a context from the account manager, + * use dc_accounts_remove_account(). + * + * @memberof dc_accounts_t + * @param dir The directory to create the context-databases in. + * If the directory does not exist, + * dc_accounts_new_with_event_channel() will try to create it. + * @param writable Whether the returned account manager is writable, i.e. calling these functions on + * it is possible: dc_accounts_add_account(), dc_accounts_add_closed_account(), + * dc_accounts_migrate_account(), dc_accounts_remove_account(), dc_accounts_select_account(). + * @param dc_event_channel_t Events Channel to be used for this accounts manager, + * create one with dc_event_channel_new(). + * This channel is consumed by this method and can not be used again afterwards, + * so be sure to call `dc_event_channel_get_event_emitter` before. + * @return An account manager object. + * The object must be passed to the other account manager functions + * and must be freed using dc_accounts_unref() after usage. + * On errors, NULL is returned. + */ +dc_accounts_t* dc_accounts_new_with_event_channel(const char* dir, int writable, dc_event_channel_t* events_channel); /** * Free an account manager object. @@ -5999,6 +6029,58 @@ char* dc_jsonrpc_next_response(dc_jsonrpc_instance_t* jsonrpc_instance); */ char* dc_jsonrpc_blocking_call(dc_jsonrpc_instance_t* jsonrpc_instance, const char *input); +/** + * @class dc_event_channel_t + * + * Opaque object that is used to create an event emitter which can be used log events during startup of an accounts manger. + * Only used for dc_accounts_new_with_event_channel(). + * To use it: + * 1. create an events channel with `dc_event_channel_new()`. + * 2. get an event emitter for it with `dc_event_channel_get_event_emitter()`. + * 3. use it to create your account manager with `dc_accounts_new_with_event_channel()`, which consumes the channel. + * 4. free the empty channel wrapper object with `dc_event_channel_unref()`. + */ + + /** + * Create a new event channel. + * + * @memberof dc_event_channel_t + * @return An event channel wrapper object (dc_event_channel_t). + */ + dc_event_channel_t* dc_event_channel_new(); + + /** + * Release/free the events channel structure. + * This function releases the memory of the `dc_event_channel_t` structure. + * + * you can call it after calling dc_accounts_new_with_event_channel, + * which took the events channel out of it already, so this just frees the underlying option. + * + * @memberof dc_event_channel_t + */ +void dc_event_channel_unref(dc_event_channel_t* event_channel); + +/** + * Create the event emitter that is used to receive events. + * + * The library will emit various @ref DC_EVENT events, such as "new message", "message read" etc. + * To get these events, you have to create an event emitter using this function + * and call dc_get_next_event() on the emitter. + * + * This is similar to dc_get_event_emitter(), which, however, + * must not be called for accounts handled by the account manager. + * + * @memberof dc_event_channel_t + * @param The event channel. + * @return Returns the event emitter, NULL on errors. + * Must be freed using dc_event_emitter_unref() after usage. + * + * Note: Use only one event emitter per account manager. + * Having more than one event emitter running at the same time on the same account manager + * will result in events randomly delivered to the one or to the other. + */ +dc_event_emitter_t* dc_event_channel_get_event_emitter(dc_event_channel_t* event_channel); + /** * @class dc_event_emitter_t * diff --git a/deltachat-ffi/src/lib.rs b/deltachat-ffi/src/lib.rs index e92f30a553..3f7c57841c 100644 --- a/deltachat-ffi/src/lib.rs +++ b/deltachat-ffi/src/lib.rs @@ -11,6 +11,7 @@ #[macro_use] extern crate human_panic; +use std::cell::Cell; use std::collections::BTreeMap; use std::convert::TryFrom; use std::fmt::Write; @@ -4785,6 +4786,91 @@ pub unsafe extern "C" fn dc_accounts_new( } } +pub type dc_event_channel_t = Cell>; + +#[no_mangle] +pub unsafe extern "C" fn dc_event_channel_new() -> *mut dc_event_channel_t { + Box::into_raw(Box::new(Cell::new(Some(Events::new())))) +} + +/// Release the events channel structure. +/// +/// This function releases the memory of the `dc_event_channel_t` structure. +/// +/// you can call it after calling dc_accounts_new_with_event_channel, +/// which took the events channel out of it already, so this just frees the underlying option. +#[no_mangle] +pub unsafe extern "C" fn dc_event_channel_unref(event_channel: *mut dc_event_channel_t) { + if event_channel.is_null() { + eprintln!("ignoring careless call to dc_event_channel_unref()"); + return; + } + let _ = Box::from_raw(event_channel); +} + +#[no_mangle] +pub unsafe extern "C" fn dc_event_channel_get_event_emitter( + event_channel: *mut dc_event_channel_t, +) -> *mut dc_event_emitter_t { + if event_channel.is_null() { + eprintln!("ignoring careless call to dc_event_channel_get_event_emitter()"); + return ptr::null_mut(); + } + + let Some(event_channel) = &*(*event_channel).as_ptr() else { + eprintln!( + "ignoring careless call to dc_event_channel_get_event_emitter() + -> channel was already consumed, make sure you call this before dc_accounts_new_with_event_channel" + ); + return ptr::null_mut(); + }; + + let emitter = event_channel.get_emitter(); + + Box::into_raw(Box::new(emitter)) +} + +#[no_mangle] +pub unsafe extern "C" fn dc_accounts_new_with_event_channel( + dir: *const libc::c_char, + writable: libc::c_int, + event_channel: *mut dc_event_channel_t, +) -> *mut dc_accounts_t { + setup_panic!(); + + if dir.is_null() || event_channel.is_null() { + eprintln!("ignoring careless call to dc_accounts_new_with_event_channel()"); + return ptr::null_mut(); + } + + // consuming channel enforce that you need to get the event emitter + // before initializing the account manager, + // so that you don't miss events/errors during initialisation. + // It also prevents you from using the same channel on multiple account managers. + let Some(event_channel) = (*event_channel).take() else { + eprintln!( + "ignoring careless call to dc_accounts_new_with_event_channel() + -> channel was already consumed" + ); + return ptr::null_mut(); + }; + + let accs = block_on(Accounts::new_with_events( + as_path(dir).into(), + writable != 0, + event_channel, + )); + + match accs { + Ok(accs) => Box::into_raw(Box::new(AccountsWrapper::new(accs))), + Err(err) => { + // We are using Anyhow's .context() and to show the inner error, too, we need the {:#}: + eprintln!("failed to create accounts: {err:#}"); + ptr::null_mut() + } + } +} + /// Release the accounts structure. /// /// This function releases the memory of the `dc_accounts_t` structure. diff --git a/src/accounts.rs b/src/accounts.rs index a0f93bdf8f..5d86668abb 100644 --- a/src/accounts.rs +++ b/src/accounts.rs @@ -60,8 +60,18 @@ impl Accounts { if writable && !dir.exists() { Accounts::create(&dir).await?; } + let events = Events::new(); + Accounts::open(events, dir, writable).await + } - Accounts::open(dir, writable).await + /// Loads or creates an accounts folder at the given `dir`. + /// Uses an existing events channel. + pub async fn new_with_events(dir: PathBuf, writable: bool, events: Events) -> Result { + if writable && !dir.exists() { + Accounts::create(&dir).await?; + } + + Accounts::open(events, dir, writable).await } /// Get the ID used to log events. @@ -85,14 +95,14 @@ impl Accounts { /// Opens an existing accounts structure. Will error if the folder doesn't exist, /// no account exists and no config exists. - async fn open(dir: PathBuf, writable: bool) -> Result { + async fn open(events: Events, dir: PathBuf, writable: bool) -> Result { ensure!(dir.exists(), "directory does not exist"); let config_file = dir.join(CONFIG_NAME); ensure!(config_file.exists(), "{config_file:?} does not exist"); let config = Config::from_file(config_file, writable).await?; - let events = Events::new(); + let stockstrings = StockStrings::new(); let push_subscriber = PushSubscriber::new(); let accounts = config