diff --git a/Cargo.lock b/Cargo.lock index 68b08bea6..a82eb8ca5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,9 +17,9 @@ checksum = "ee2a4ec343196209d6594e19543ae87a39f96d5534d7174822a3ad825dd6ed7e" [[package]] name = "adler32" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "567b077b825e468cc974f0020d4082ee6e03132512f207ef1a02fd5d00d1f32d" +checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234" [[package]] name = "aead" @@ -27,7 +27,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7fc95d1bdb8e6666b2b217308eeeb09f2d6728d104be3e31916cc74d15420331" dependencies = [ - "generic-array 0.14.3", + "generic-array 0.14.4", ] [[package]] @@ -138,9 +138,9 @@ dependencies = [ [[package]] name = "async-channel" -version = "1.2.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "958a8af017616083a7c739a9c4da4b757a6816593734b4b6145adbe1421526a5" +checksum = "43de69555a39d52918e2bc33a408d3c0a86c829b212d898f4ca25d21a6387478" dependencies = [ "concurrent-queue", "event-listener", @@ -149,9 +149,9 @@ dependencies = [ [[package]] name = "async-h1" -version = "2.1.1" +version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63da30f1df6a14c2e111596727d1e9dbd593a749dd66f0a08da005733e2a880e" +checksum = "7ca2b5cfe1804f48bb8dfb1b2391e6e9a3fbf89e07514dce3bddb03eb4d529db" dependencies = [ "async-std", "byte-pool", @@ -401,7 +401,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" dependencies = [ "block-padding 0.2.0", - "generic-array 0.14.3", + "generic-array 0.14.4", ] [[package]] @@ -410,7 +410,7 @@ version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fa136449e765dc7faa244561ccae839c394048667929af599b5d931ebe7b7f10" dependencies = [ - "generic-array 0.14.3", + "generic-array 0.14.4", ] [[package]] @@ -615,9 +615,9 @@ checksum = "0dbbb57365263e881e805dc77d94697c9118fd94d8da011240555aa7b23445bd" [[package]] name = "concurrent-queue" -version = "1.1.2" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1582139bb74d97ef232c30bc236646017db06f13ee7cc01fa24c9e55640f86d4" +checksum = "30ed07550be01594c6026cff2a1d7fe9c8f683caa798e12b68694ac9e88286a3" dependencies = [ "cache-padded", ] @@ -710,7 +710,7 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b584a330336237c1eecd3e94266efb216c56ed91225d634cb2991c5f3fd1aeab" dependencies = [ - "generic-array 0.14.3", + "generic-array 0.14.4", "subtle", ] @@ -774,9 +774,9 @@ dependencies = [ [[package]] name = "data-encoding" -version = "2.2.1" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72aa14c04dfae8dd7d8a2b1cb7ca2152618cd01336dbfe704b8dcbf8d41dbd69" +checksum = "d4d0e2d24e5ee3b23a01de38eefdcd978907890701f08ffffd4cb457ca4ee8d6" [[package]] name = "deflate" @@ -848,7 +848,9 @@ dependencies = [ "surf", "tempfile", "thiserror", + "toml", "url", + "uuid", ] [[package]] @@ -869,6 +871,7 @@ dependencies = [ "human-panic", "libc", "num-traits", + "rand", "serde_json", "thiserror", ] @@ -930,7 +933,7 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" dependencies = [ - "generic-array 0.14.3", + "generic-array 0.14.4", ] [[package]] @@ -1095,9 +1098,9 @@ checksum = "b5320ae4c3782150d900b79807611a59a99fc9a1d61d686faafc24b93fc8d7ca" [[package]] name = "enum-as-inner" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc4bfcfacb61d231109d1d55202c1f33263319668b168843e02ad4652725ec9c" +checksum = "7c5f0096a91d210159eceb2ff5e1c4da18388a170e1e3ce948aac9c8fdbbf595" dependencies = [ "heck", "proc-macro2", @@ -1120,9 +1123,9 @@ dependencies = [ [[package]] name = "error-chain" -version = "0.12.2" +version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d371106cc88ffdfb1eabd7111e432da544f16f3e2d7bf1dfe8bf575f1df045cd" +checksum = "2d2f06b9cac1506ece98fe3231e3cc9c4410ec3d5b1f24ae1c8946f0742cdefc" dependencies = [ "backtrace", "version_check 0.9.2", @@ -1139,9 +1142,9 @@ dependencies = [ [[package]] name = "event-listener" -version = "2.2.1" +version = "2.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "829694371bd7bbc6aee17c4ff624aad8bf9f4dc06c6f9f6071eaa08c89530d10" +checksum = "7f14646a9e0430150a87951622ba9675472b68e384b7701b8423b30560805c7a" [[package]] name = "fake-simd" @@ -1172,9 +1175,9 @@ dependencies = [ [[package]] name = "fastrand" -version = "1.3.3" +version = "1.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36a9cb09840f81cd211e435d00a4e487edd263dc3c8ff815c32dd76ad668ebed" +checksum = "4bd3bdaaf0a72155260a1c098989b60db1cbb22d6a628e64f16237aa4da93cc7" [[package]] name = "flate2" @@ -1348,9 +1351,9 @@ dependencies = [ [[package]] name = "generic-array" -version = "0.14.3" +version = "0.14.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60fb4bb6bba52f78a471264d9a3b7d026cc0af47b22cd2cffbc0b787ca003e63" +checksum = "501466ecc8a30d1d3b7fc9229b122b2ce8ed6e9d9223f1138d4babb253e51817" dependencies = [ "typenum", "version_check 0.9.2", @@ -1407,9 +1410,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34f595585f103464d8d2f6e9864682d74c1601fed5e07d62b1c9058dba8246fb" +checksum = "e91b62f79061a0bc2e046024cb7ba44b08419ed238ecbd9adbd787434b9e8c25" dependencies = [ "autocfg 1.0.0", ] @@ -1499,9 +1502,9 @@ dependencies = [ [[package]] name = "http-types" -version = "2.3.0" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bc341c4b7a71eb0d85c760dc7363648b82ecc38fa5ead50f69b52858df708b9" +checksum = "bb4daf8dc001485f4a32a7a17c54c67fa8a10340188f30ba87ac0fe1a9451e97" dependencies = [ "anyhow", "async-std", @@ -1590,9 +1593,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b88cd59ee5f71fea89a62248fc8f387d44400cefe05ef548466d61ced9029a7" +checksum = "86b45e59b16c76b11bf9738fd5d38879d3bd28ad292d7b313608becb17ae2df9" dependencies = [ "autocfg 1.0.0", "hashbrown", @@ -1666,9 +1669,9 @@ dependencies = [ [[package]] name = "kamadak-exif" -version = "0.5.1" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a5e66d5b5469321038611f7f0e845a48989e4fd54987b6e5bb4c8ae3adbace7" +checksum = "524f22a6373f7d4f4e7caa44d54efbaf1e92c09d41f38647db2784ebce610aa8" dependencies = [ "mutate_once", ] @@ -2194,7 +2197,7 @@ dependencies = [ "digest 0.9.0", "ed25519-dalek", "flate2", - "generic-array 0.14.3", + "generic-array 0.14.4", "hex", "lazy_static", "log", @@ -2991,7 +2994,7 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09f8ed9974042b8c3672ff3030a69fcc03b74c47c3d1ecb7755e8a3626011e88" dependencies = [ - "generic-array 0.14.3", + "generic-array 0.14.4", ] [[package]] @@ -3046,9 +3049,9 @@ dependencies = [ [[package]] name = "syn" -version = "1.0.36" +version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cdb98bcb1f9d81d07b536179c269ea15999b5d14ea958196413869445bb5250" +checksum = "e69abc24912995b3038597a7a593be5053eb0fb44f3cc5beec0deb421790c1f4" dependencies = [ "proc-macro2", "quote", @@ -3308,7 +3311,7 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8326b2c654932e3e4f9196e69d08fdf7cfd718e1dc6f66b347e6024a0c961402" dependencies = [ - "generic-array 0.14.3", + "generic-array 0.14.4", "subtle", ] @@ -3321,6 +3324,7 @@ dependencies = [ "idna", "matches", "percent-encoding", + "serde", ] [[package]] @@ -3336,6 +3340,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9fde2f6a4bea1d6e007c4ad38c6839fa71cbb63b6dbf5b595aa38dc9b1093c11" dependencies = [ "rand", + "serde", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 025a92291..fdce41407 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -59,11 +59,13 @@ anyhow = "1.0.28" async-trait = "0.1.31" url = "2.1.1" async-std-resolver = "0.19.5" +uuid = { version = "0.8", features = ["serde", "v4"] } pretty_env_logger = { version = "0.4.0", optional = true } log = {version = "0.4.8", optional = true } rustyline = { version = "4.1.0", optional = true } ansi_term = { version = "0.12.1", optional = true } +toml = "0.5.6" [dev-dependencies] diff --git a/deltachat-ffi/Cargo.toml b/deltachat-ffi/Cargo.toml index 7f10d5e00..5b346bd5d 100644 --- a/deltachat-ffi/Cargo.toml +++ b/deltachat-ffi/Cargo.toml @@ -23,6 +23,7 @@ serde_json = "1.0" async-std = "1.6.0" anyhow = "1.0.28" thiserror = "1.0.14" +rand = "0.7.3" [features] default = ["vendored"] diff --git a/deltachat-ffi/deltachat.h b/deltachat-ffi/deltachat.h index 7b769cca6..9db239080 100644 --- a/deltachat-ffi/deltachat.h +++ b/deltachat-ffi/deltachat.h @@ -12,6 +12,7 @@ extern "C" { typedef struct _dc_context dc_context_t; +typedef struct _dc_accounts dc_accounts_t; typedef struct _dc_array dc_array_t; typedef struct _dc_chatlist dc_chatlist_t; typedef struct _dc_chat dc_chat_t; @@ -21,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_accounts_event_emitter dc_accounts_event_emitter_t; /** @@ -194,6 +196,9 @@ typedef struct _dc_event_emitter dc_event_emitter_t; * @return A context object with some public members. * The object must be passed to the other context functions * and must be freed using dc_context_unref() after usage. + * + * If you want to use multiple context objects at the same time, + * this can be managed using dc_accounts_t. */ dc_context_t* dc_context_new (const char* os_name, const char* dbfile, const char* blobdir); @@ -205,14 +210,33 @@ dc_context_t* dc_context_new (const char* os_name, const char* d * are closed. You can also do this explicitly by calling dc_close() on your own * before calling dc_context_unref(). * + * You have to call this function + * also for accounts returned by dc_accounts_get_account() or dc_accounts_get_selected_account(), + * however, in this case, the context is not shut down but just a reference counter is decreased + * * @memberof dc_context_t - * @param context The context object as created by dc_context_new(). + * @param context The context object as created by dc_context_new(), + * dc_accounts_get_account() or dc_accounts_get_selected_account(). * If NULL is given, nothing is done. * @return None. */ void dc_context_unref (dc_context_t* context); +/** + * Get the ID of a context object. + * Each context has an ID assigned. + * If the context was created through the dc_accounts_t account manager, + * the ID is unique, no other context handled by the account manager will have the same ID. + * If the context was created by dc_context_new(), a random ID is assigned. + * + * @memberof dc_context_t + * @param context The context object as created eg. by dc_accounts_get_account() or dc_context_new(). + * @return The context-id. + */ +uint32_t dc_get_id (dc_context_t* context); + + /** * Create the event emitter that is used to receive events. * The library will emit various @ref DC_EVENT events as "new message", "message read" etc. @@ -235,7 +259,7 @@ dc_event_emitter_t* dc_get_event_emitter(dc_context_t* context); * Get the blob directory. * * @memberof dc_context_t - * @param context The context object as created by dc_context_new(). + * @param context The context object. * @return Blob directory associated with the context object, empty string if unset or on errors. NULL is never returned. * The returned string must be released using dc_str_unref(). */ @@ -328,7 +352,7 @@ char* dc_get_blobdir (const dc_context_t* context); * If you want to retrieve a value, use dc_get_config(). * * @memberof dc_context_t - * @param context The context object + * @param context The context object. * @param key The option to change, see above. * @param value The value to save for "key" * @return 0=failure, 1=success @@ -352,7 +376,7 @@ int dc_set_config (dc_context_t* context, const char* * The config-keys are the keys that can be passed to the parameter `key` of this function. * * @memberof dc_context_t - * @param context The context object as created by dc_context_new(). For querying system values, this can be NULL. + * @param context The context object. For querying system values, this can be NULL. * @param key The key to query. * @return Returns current value of "key", if "key" is unset, the default * value is returned. The returned value must be released using dc_str_unref(), NULL is never @@ -403,7 +427,7 @@ int dc_set_config_from_qr (dc_context_t* context, const char* qr); * included when however. * * @memberof dc_context_t - * @param context The context as created by dc_context_new(). + * @param context The context object. * @return String which must be released using dc_str_unref() after usage. Never returns NULL. */ char* dc_get_info (dc_context_t* context); @@ -425,7 +449,7 @@ char* dc_get_info (dc_context_t* context); * dc_configure() can be called as usual afterwards. * * @memberof dc_context_t - * @param context The context object as created by dc_context_new(). + * @param context The context object. * @param addr E-mail address the user has entered. * In case the user selects a different e-mail-address during * authorization, this is corrected in dc_configure() @@ -479,7 +503,7 @@ char* dc_get_oauth2_url (dc_context_t* context, const char* * and parameters as `addr`, `mail_pw` etc. are set to that. * * @memberof dc_context_t - * @param context The context object as created by dc_context_new(). + * @param context The context object. * @return None. * * There is no need to call dc_configure() on every program start, @@ -503,7 +527,7 @@ void dc_configure (dc_context_t* context); * to enter some settings and dc_configure() is called in a thread then. * * @memberof dc_context_t - * @param context The context object as created by dc_context_new(). + * @param context The context object. * @return 1=context is configured and can be used; * 0=context is not configured and a configuration by dc_configure() is required. */ @@ -515,6 +539,9 @@ int dc_is_configured (const dc_context_t* context); * If IO is already running, nothing happens. * To check the current IO state, use dc_is_io_running(). * + * If the context was created by the dc_accounts_t account manager, + * use dc_accounts_start_io() instead of this function. + * * @memberof dc_context_t * @param context The context object as created by dc_context_new(). * @return None @@ -525,7 +552,7 @@ void dc_start_io (dc_context_t* context); * Check if IO (SMTP/IMAP/Jobs) has been started. * * @memberof dc_context_t - * @param context The context object as created by dc_context_new(). + * @param context The context object. * @return 1=IO is running; * 0=IO is not running. */ @@ -536,6 +563,9 @@ int dc_is_io_running(const dc_context_t* context); * If IO is not running, nothing happens. * To check the current IO state, use dc_is_io_running(). * + * If the context was created by the dc_accounts_t account manager, + * use dc_accounts_stop_io() instead of this function. + * * @memberof dc_context_t * @param context The context object as created by dc_context_new(). * @return None @@ -558,6 +588,9 @@ void dc_stop_io(dc_context_t* context); * and will led to let the jobs fail faster, with fewer retries * and may avoid messages being sent out. * + * Finally, if the context was created by the dc_accounts_t account manager, + * use dc_accounts_maybe_network() instead of this function. + * * @memberof dc_context_t * @param context The context as created by dc_context_new(). * @return None. @@ -869,7 +902,7 @@ uint32_t dc_send_text_msg (dc_context_t* context, uint32_t ch * However, UIs might some things differently, eg. play a different sound. * * @memberof dc_context_t - * @param context The context object. + * @param context The context object * @param chat_id The chat to start a videochat for. * @return The id if the message sent out * or 0 for errors. @@ -894,7 +927,7 @@ uint32_t dc_send_videochat_invitation (dc_context_t* context, uint32_t chat_id); * If the draft is modified, an #DC_EVENT_MSGS_CHANGED will be sent. * * @memberof dc_context_t - * @param context The context as created by dc_context_new(). + * @param context The context object. * @param chat_id The chat ID to save the draft for. * @param msg The message to save as a draft. * Existing draft will be overwritten. @@ -919,7 +952,7 @@ void dc_set_draft (dc_context_t* context, uint32_t ch * To check, if a given chat is a device-chat, see dc_chat_is_device_talk() * * @memberof dc_context_t - * @param context The context as created by dc_context_new(). + * @param context The context object. * @param label A unique name for the message to add. * The label is typically not displayed to the user and * must be created from the characters `A-Z`, `a-z`, `0-9`, `_` or `-`. @@ -968,7 +1001,7 @@ uint32_t dc_add_device_msg (dc_context_t* context, const char* * (however, not seen device messages are added and may re-create the device-chat). * * @memberof dc_context_t - * @param context The context as created by dc_context_new(). + * @param context The context object. * @return None. */ void dc_update_device_chats (dc_context_t* context); @@ -979,7 +1012,7 @@ void dc_update_device_chats (dc_context_t* context); * Device-messages can be added dc_add_device_msg(). * * @memberof dc_context_t - * @param context The context as created by dc_context_new(). + * @param context The context object. * @param label Label of the message to check. * @return 1=A message with this label was added at some point, * 0=A message with this label was never added. @@ -992,7 +1025,7 @@ int dc_was_device_msg_ever_added (dc_context_t* context, const char* * See dc_set_draft() for more details about drafts. * * @memberof dc_context_t - * @param context The context as created by dc_context_new(). + * @param context The context object. * @param chat_id The chat ID to get the draft for. * @return Message object. * Can be passed directly to dc_send_msg(). @@ -1219,7 +1252,7 @@ dc_array_t* dc_get_chat_contacts (dc_context_t* context, uint32_t ch * Get the chat's ephemeral message timer. * * @memberof dc_context_t - * @param context The context as created by dc_context_new(). + * @param context The context object. * @param chat_id The chat ID. * * @return ephemeral timer value in seconds, 0 if the timer is disabled or if there is an error @@ -1280,7 +1313,7 @@ dc_chat_t* dc_get_chat (dc_context_t* context, uint32_t ch * This may be useful if you want to show some help for just created groups. * * @memberof dc_context_t - * @param context The context as created by dc_context_new(). + * @param context The context object. * @param verified If set to 1 the function creates a secure verified group. * Only secure-verified members are allowed in these groups * and end-to-end-encryption is always enabled. @@ -1296,7 +1329,7 @@ uint32_t dc_create_group_chat (dc_context_t* context, int verifie * Check if a given contact ID is a member of a group chat. * * @memberof dc_context_t - * @param context The context as created by dc_context_new(). + * @param context The context object. * @param chat_id The chat ID to check. * @param contact_id The contact ID to check. To check if yourself is member * of the chat, pass DC_CONTACT_ID_SELF (1) here. @@ -1316,7 +1349,7 @@ int dc_is_contact_in_chat (dc_context_t* context, uint32_t ch * Sends out #DC_EVENT_CHAT_MODIFIED and #DC_EVENT_MSGS_CHANGED if a status message was sent. * * @memberof dc_context_t - * @param context The context as created by dc_context_new(). + * @param context The context object. * @param chat_id The chat ID to add the contact to. Must be a group chat. * @param contact_id The contact ID to add to the chat. * @return 1=member added to group, 0=error @@ -1333,7 +1366,7 @@ int dc_add_contact_to_chat (dc_context_t* context, uint32_t ch * Sends out #DC_EVENT_CHAT_MODIFIED and #DC_EVENT_MSGS_CHANGED if a status message was sent. * * @memberof dc_context_t - * @param context The context as created by dc_context_new(). + * @param context The context object. * @param chat_id The chat ID to remove the contact from. Must be a group chat. * @param contact_id The contact ID to remove from the chat. * @return 1=member removed from group, 0=error @@ -1352,7 +1385,7 @@ int dc_remove_contact_from_chat (dc_context_t* context, uint32_t ch * @memberof dc_context_t * @param chat_id The chat ID to set the name for. Must be a group chat. * @param name New name of the group. - * @param context The context as created by dc_context_new(). + * @param context The context object. * @return 1=success, 0=error */ int dc_set_chat_name (dc_context_t* context, uint32_t chat_id, const char* name); @@ -1365,7 +1398,7 @@ int dc_set_chat_name (dc_context_t* context, uint32_t ch * participating in a chat. * * @memberof dc_context_t - * @param context The context as created by dc_context_new(). + * @param context The context object. * @param chat_id The chat ID to set the ephemeral message timer for. * @param timer The timer value in seconds or 0 to disable the timer. * @@ -1384,7 +1417,7 @@ int dc_set_chat_ephemeral_timer (dc_context_t* context, uint32_t chat_id, uint32 * To find out the profile image of a chat, use dc_chat_get_profile_image() * * @memberof dc_context_t - * @param context The context as created by dc_context_new(). + * @param context The context object. * @param chat_id The chat ID to set the image for. * @param image Full path of the image to use as the group image. The image will immediately be copied to the * `blobdir`; the original image will not be needed anymore. @@ -1406,7 +1439,7 @@ int dc_set_chat_profile_image (dc_context_t* context, uint32_t ch * @memberof dc_context_t * @param chat_id The chat ID to set the mute duration. * @param duration The duration (0 for no mute, -1 for forever mute, everything else is is the relative mute duration from now in seconds) - * @param context The context as created by dc_context_new(). + * @param context The context object. * @return 1=success, 0=error */ int dc_set_chat_mute_duration (dc_context_t* context, uint32_t chat_id, int64_t duration); @@ -1421,7 +1454,7 @@ int dc_set_chat_mute_duration (dc_context_t* context, ui * max. text returned by dc_msg_get_text() (about 30000 characters). * * @memberof dc_context_t - * @param context The context object as created by dc_context_new(). + * @param context The context object object. * @param msg_id The message id for which information should be generated * @return Text string, must be released using dc_str_unref() after usage */ @@ -1435,7 +1468,7 @@ char* dc_get_msg_info (dc_context_t* context, uint32_t ms * was called before. * * @memberof dc_context_t - * @param context The context object as created by dc_context_new(). + * @param context The context object. * @param msg_id The message id, must be the id of an incoming message. * @return Raw headers as a multi-line string, must be released using dc_str_unref() after usage. * Returns NULL if there are no headers saved for the given message, @@ -1450,7 +1483,7 @@ char* dc_get_mime_headers (dc_context_t* context, uint32_t ms * on the IMAP server. * * @memberof dc_context_t - * @param context The context object as created by dc_context_new() + * @param context The context object * @param msg_ids an array of uint32_t containing all message IDs that should be deleted * @param msg_cnt The number of messages IDs in the msg_ids array * @return None. @@ -1462,7 +1495,7 @@ void dc_delete_msgs (dc_context_t* context, const uint3 * Deprecated, use dc_set_config() with the key "delete_server_after" instead. * * @memberof dc_context_t - * @param context The context object as created by dc_context_new() + * @param context The context object. * @param flags What to delete, a combination of the @ref DC_EMPTY flags * @return None. */ @@ -1473,7 +1506,7 @@ void dc_empty_server (dc_context_t* context, uint32_t fl * Forward messages to another chat. * * @memberof dc_context_t - * @param context The context object as created by dc_context_new() + * @param context The context object. * @param msg_ids An array of uint32_t containing all message IDs that should be forwarded * @param msg_cnt The number of messages IDs in the msg_ids array * @param chat_id The destination chat ID. @@ -1490,7 +1523,7 @@ void dc_forward_msgs (dc_context_t* context, const uint3 * Calling this function usually results in the event #DC_EVENT_MSGS_CHANGED. * * @memberof dc_context_t - * @param context The context object as created by dc_context_new() + * @param context The context object. * @param contact_id The contact ID of which all messages should be marked as noticed. * @return None. */ @@ -1518,7 +1551,7 @@ void dc_markseen_msgs (dc_context_t* context, const uint3 * dc_get_chat_msgs() using the chat_id DC_CHAT_ID_STARRED. * * @memberof dc_context_t - * @param context The context object as created by dc_context_new() + * @param context The context object. * @param msg_ids An array of uint32_t message IDs defining the messages to star or unstar * @param msg_cnt The number of IDs in msg_ids * @param star 0=unstar the messages in msg_ids, 1=star them @@ -1533,7 +1566,7 @@ void dc_star_msgs (dc_context_t* context, const uint3 * For a list or chats, see dc_get_chatlist() * * @memberof dc_context_t - * @param context The context as created by dc_context_new(). + * @param context The context object. * @param msg_id The message ID for which the message object should be created. * @return A dc_msg_t message object. * On errors, NULL is returned. @@ -1568,7 +1601,7 @@ int dc_may_be_valid_addr (const char* addr); * use dc_may_be_valid_addr(). * * @memberof dc_context_t - * @param context The context object as created by dc_context_new(). + * @param context The context object. * @param addr The e-mail-address to check. * @return Contact ID of the contact belonging to the e-mail-address * or 0 if there is no contact that is or was introduced by an accepted contact. @@ -1588,7 +1621,7 @@ uint32_t dc_lookup_contact_id_by_addr (dc_context_t* context, const char* * May result in a #DC_EVENT_CONTACTS_CHANGED event. * * @memberof dc_context_t - * @param context The context object as created by dc_context_new(). + * @param context The context object. * @param name Name of the contact to add. If you do not know the name belonging * to the address, you can give NULL here. * @param addr E-mail-address of the contact to add. If the email address @@ -1619,7 +1652,7 @@ uint32_t dc_create_contact (dc_context_t* context, const char* * however, for adding a bunch of addresses, this function is _much_ faster. * * @memberof dc_context_t - * @param context the context object as created by dc_context_new(). + * @param context the context object. * @param addr_book A multi-line string in the format * `Name one\nAddress one\nName two\nAddress two`. * If an email address already exists, the name is updated @@ -1635,7 +1668,7 @@ int dc_add_address_book (dc_context_t* context, const char* * To get information about a single contact, see dc_get_contact(). * * @memberof dc_context_t - * @param context The context object as created by dc_context_new(). + * @param context The context object. * @param flags A combination of flags: * - if the flag DC_GCL_ADD_SELF is set, SELF is added to the list unless filtered by other parameters * - if the flag DC_GCL_VERIFIED_ONLY is set, only verified contacts are returned. @@ -1652,7 +1685,7 @@ dc_array_t* dc_get_contacts (dc_context_t* context, uint32_t fl * Get the number of blocked contacts. * * @memberof dc_context_t - * @param context The context object as created by dc_context_new(). + * @param context The context object. * @return The number of blocked contacts. */ int dc_get_blocked_cnt (dc_context_t* context); @@ -1662,7 +1695,7 @@ int dc_get_blocked_cnt (dc_context_t* context); * Get blocked contacts. * * @memberof dc_context_t - * @param context The context object as created by dc_context_new(). + * @param context The context object. * @return An array containing all blocked contact IDs. Must be dc_array_unref()'d * after usage. */ @@ -1674,7 +1707,7 @@ dc_array_t* dc_get_blocked_contacts (dc_context_t* context); * May result in a #DC_EVENT_CONTACTS_CHANGED event. * * @memberof dc_context_t - * @param context The context object as created by dc_context_new(). + * @param context The context object. * @param contact_id The ID of the contact to block or unblock. * @param block 1=block contact, 0=unblock contact * @return None. @@ -1688,7 +1721,7 @@ void dc_block_contact (dc_context_t* context, uint32_t co * fingerprint of the contact, used eg. to compare the fingerprints for a simple out-of-band verification. * * @memberof dc_context_t - * @param context The context object as created by dc_context_new(). + * @param context The context object. * @param contact_id ID of the contact to get the encryption info for. * @return Multi-line text, must be released using dc_str_unref() after usage. */ @@ -1702,7 +1735,7 @@ char* dc_get_contact_encrinfo (dc_context_t* context, uint32_t co * May result in a #DC_EVENT_CONTACTS_CHANGED event. * * @memberof dc_context_t - * @param context The context object as created by dc_context_new(). + * @param context The context object. * @param contact_id ID of the contact to delete. * @return 1=success, 0=error */ @@ -1717,7 +1750,7 @@ int dc_delete_contact (dc_context_t* context, uint32_t co * defined by dc_set_config(). * * @memberof dc_context_t - * @param context The context object as created by dc_context_new(). + * @param context The context object. * @param contact_id ID of the contact to get the object for. * @return The contact object, must be freed using dc_contact_unref() when no * longer used. NULL on errors. @@ -1768,7 +1801,7 @@ dc_contact_t* dc_get_contact (dc_context_t* context, uint32_t co * To cancel an import-/export-progress, use dc_stop_ongoing_process(). * * @memberof dc_context_t - * @param context The context as created by dc_context_new(). + * @param context The context. * @param what One of the DC_IMEX_* constants. * @param param1 Meaning depends on the DC_IMEX_* constants. If this parameter is a directory, it should not end with * a slash (otherwise you'll get double slashes when receiving #DC_EVENT_IMEX_FILE_WRITTEN). Set to NULL if not used. @@ -1821,7 +1854,7 @@ void dc_imex (dc_context_t* context, int what, c * ~~~ * * @memberof dc_context_t - * @param context The context as created by dc_context_new(). + * @param context The context object. * @param dir Directory to search backups in. * @return String with the backup file, typically given to dc_imex(), * returned strings must be released using dc_str_unref(). @@ -2188,6 +2221,239 @@ void dc_delete_all_locations (dc_context_t* context); void dc_str_unref (char* str); +/** + * @class dc_accounts_t + * + * This class provides functionality that can be used to + * manage several dc_context_t objects running at the same time. + * The account manager takes a directory where all + * context-databases are created in. + * + * You can add, remove, import account to the account manager, + * all context-databases are persisted and stay available once the + * account manager is created again for the same directory. + * + * All accounts may receive messages at the same time (eg. by #DC_EVENT_INCOMING_MSG), + * and all accounts may be accessed by their own dc_context_t object. + * + * To make this possible, some dc_context_t functions must not be called + * when using the account manager: + * - use dc_accounts_add_account() and dc_accounts_get_account() instead of dc_context_new() + * - use dc_accounts_start_io() and dc_accounts_stop_io() instead of dc_start_io() and dc_stop_io() + * - use dc_accounts_maybe_network() instead of dc_maybe_network() + * - use dc_accounts_get_event_emitter() instead of dc_get_event_emitter() + * + * Additionally, there are functions to list, import and migrate accounts + * and to handle a "selected" account, see below. + */ + +/** + * Create a new account manager. + * 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(), dc_accounts_import_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 os_name + * @param dir The directory to create the context-databases in. + * If the directory does not exist, + * dc_accounts_new() will try to create it. + * @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 (const char* os_name, const char* dir); + + +/** + * Free an account manager object. + * + * @memberof dc_accounts_t + * @param accounts Account manager as created by dc_accounts_new(). + */ +void dc_accounts_unref (dc_accounts_t* accounts); + + +/** + * Add a new account to the account manager. + * Internally, dc_context_new() is called using a unique database-name + * in the directory specified at dc_accounts_new(). + * + * If the function succceeds, + * dc_accounts_get_all() will return one more account + * and you can access the newly created account using dc_accounts_get_account(). + * Moreover, the newly created account will be the selected one. + * + * @memberof dc_accounts_t + * @param accounts Account manager as created by dc_accounts_new(). + * @return Account-id, use dc_accounts_get_account() to get the context object. + * On errors, 0 is returned. + */ +uint32_t dc_accounts_add_account (dc_accounts_t* accounts); + + +/** + * Import a tarfile-backup to the account manager. + * On success, a new account is added to the account-manager, + * with all the data provided by the backup-file. + * Moreover, the newly created account will be the selected one. + * + * @memberof dc_accounts_t + * @param accounts Account manager as created by dc_accounts_new(). + * @param tarfile Backup as created by dc_imex(). + * @return Account-id, use dc_accounts_get_account() to get the context object. + * On errors, 0 is returned. + */ +uint32_t dc_accounts_import_account (dc_accounts_t* accounts, const char* tarfile); + + +/** + * Migrate independent accounts into accounts managed by the account mangager. + * This will _move_ the database-file and all blob-files to the directory managed + * by the account-manager + * (to save disk-space on small devices, the files are not _copied_ + * Once the migration is done, the original file is no longer existent). + * Moreover, the newly created account will be the selected one. + * + * @memberof dc_accounts_t + * @param accounts Account manager as created by dc_accounts_new(). + * @param dbfile Unmanaged database-file that was created at some point using dc_context_new(). + * @return Account-id, use dc_accounts_get_account() to get the context object. + * On errors, 0 is returned. + */ +uint32_t dc_accounts_migrate_account (dc_accounts_t* accounts, const char* dbfile); + + +/** + * Remove an account from the account manager. + * This also removes the database-file and all blobs physically. + * If the removed account is the selected account, + * one of the other accounts will be selected. + * + * @memberof dc_accounts_t + * @param accounts Account manager as created by dc_accounts_new(). + * @return 1=success, 0=error + */ +int dc_accounts_remove_account (dc_accounts_t* accounts, uint32_t); + + +/** + * List all accounts. + * + * @memberof dc_accounts_t + * @param accounts Account manager as created by dc_accounts_new(). + * @return An array containing all account-ids, + * use dc_array_get_id() to get the ids. + */ +dc_array_t* dc_accounts_get_all (dc_accounts_t* accounts); + + +/** + * Get an account-context from an account-id. + * + * @memberof dc_accounts_t + * @param accounts Account manager as created by dc_accounts_new(). + * @param account_id The account-id as returned eg. by dc_accounts_get_all() or dc_accounts_add_account(). + * @return The account-context, this can be used most similar as a normal, + * unmanaged account-context as created by dc_context_new(). + * Once you do no longer need the context-object, you have to call dc_context_unref() on it, + * which, however, will not close the account but only decrease a reference counter. + */ +dc_context_t* dc_accounts_get_account (dc_accounts_t* accounts, uint32_t account_id); + + +/** + * Get the currently selected account. + * If there is at least once account in the account-manager, + * there is always a selected one. + * To change the selected account, use dc_accounts_select_account(); + * also adding/importing/migrating accounts may change the selection. + * + * @memberof dc_accounts_t + * @param accounts Account manager as created by dc_accounts_new(). + * @return The account-context, this can be used most similar as a normal, + * unmanaged account-context as created by dc_context_new(). + * Once you do no longer need the context-object, you have to call dc_context_unref() on it, + * which, however, will not close the account but only decrease a reference counter. + */ +dc_context_t* dc_accounts_get_selected_account (dc_accounts_t* accounts); + + +/** + * Change the selected account. + * + * @memberof dc_accounts_t + * @param accounts Account manager as created by dc_accounts_new(). + * @param account_id The account-id as returned eg. by dc_accounts_get_all() or dc_accounts_add_account(). + * @return 1=success, 0=error + */ +int dc_accounts_select_account (dc_accounts_t* accounts, uint32_t account_id); + + +/** + * Start job and IMAP/SMTP tasks for all accounts managed by the account manager. + * If IO is already running, nothing happens. + * This is similar to dc_start_io(), which, however, + * must not be called for accounts handled by the account manager. + * + * @memberof dc_accounts_t + * @param accounts Account manager as created by dc_accounts_new(). + * @return None. + */ +void dc_accounts_start_io (dc_accounts_t* accounts); + + +/** + * Stop job and IMAP/SMTP tasks for all accounts and return when they are finished. + * If IO is not running, nothing happens. + * This is similar to dc_stop_io(), which, however, + * must not be called for accounts handled by the account manager. + * + * @memberof dc_accounts_t + * @param accounts Account manager as created by dc_accounts_new(). + */ +void dc_accounts_stop_io (dc_accounts_t* accounts); + + +/** + * This function should be called when there is a hint + * that the network is available again. + * This is similar to dc_maybe_network(), which, however, + * must not be called for accounts handled by the account manager. + * + * @memberof dc_accounts_t + * @param accounts Account manager as created by dc_accounts_new(). + */ +void dc_accounts_maybe_network (dc_accounts_t* accounts); + + +/** + * Create the event emitter that is used to receive events. + * + * The library will emit various @ref DC_EVENT events as "new message", "message read" etc. + * To get these events, you have to create an event emitter using this function + * and call dc_accounts_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_accounts_t + * @param accounts Account manager as created by dc_accounts_new(). + * @return Returns the event emitter, NULL on errors. + * Must be freed using dc_accounts_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_accounts_event_emitter_t* dc_accounts_get_event_emitter (dc_accounts_t* accounts); + + /** * @class dc_array_t * @@ -2489,7 +2755,7 @@ dc_lot_t* dc_chatlist_get_summary (const dc_chatlist_t* chatlist, siz * use dc_chatlist_get_summary() in this case instead. * * @memberof dc_context_t - * @param context The context as created by dc_context_new() + * @param context The context object. * @param chat_id Chat to get a summary for. * @param msg_id Messasge to get a summary for. * @return The summary as an dc_lot_t object, see dc_chatlist_get_summary() for details. @@ -3641,7 +3907,7 @@ int dc_contact_is_verified (dc_contact_t* contact); * The provider is extracted from the email address and it's information is returned. * * @memberof dc_provider_t - * @param context The context object as created by dc_context_new(). + * @param context The context object. * @param email The user's email address to extract the provider info form. * @return a dc_provider_t struct which can be used with the dc_provider_get_* * accessor functions. If no provider info is found, NULL will be @@ -4025,12 +4291,14 @@ int64_t dc_lot_get_timestamp (const dc_lot_t* lot); /** * @class dc_event_emitter_t * - * Opaque object that is used to get events. + * Opaque object that is used to get events from a single context. * You can get an event emitter from a context using dc_get_event_emitter(). + * If you are using the dc_accounts_t account manager, + * dc_accounts_event_emitter_t must be used instead. */ /** - * Get the next event from an event emitter object. + * Get the next event from a context event emitter object. * * @memberof dc_event_emitter_t * @param emitter Event emitter object as returned from dc_get_event_emitter(). @@ -4044,7 +4312,7 @@ dc_event_t* dc_get_next_event(dc_event_emitter_t* emitter); /** - * Free an event emitter object. + * Free a context event emitter object. * * @memberof dc_event_emitter_t * @param emitter Event emitter object as returned from dc_get_event_emitter(). @@ -4054,6 +4322,40 @@ dc_event_t* dc_get_next_event(dc_event_emitter_t* emitter); void dc_event_emitter_unref(dc_event_emitter_t* emitter); +/** + * @class dc_accounts_event_emitter_t + * + * Opaque object that is used to get events from the dc_accounts_t account manager. + * You get an event emitter from the account manager using dc_accounts_get_event_emitter(). + * If you are not using the dc_accounts_t account manager but just a single dc_context_t object, + * dc_event_emitter_t must be used instead. + */ + +/** + * Get the next event from an accounts event emitter object. + * + * @memberof dc_accounts_event_emitter_t + * @param emitter Event emitter object as returned from dc_accounts_get_event_emitter(). + * @return An event as an dc_event_t object. + * You can query the event for information using dc_event_get_id(), dc_event_get_data1_int() and so on; + * if you are done with the event, you have to free the event using dc_event_unref(). + * If NULL is returned, the context belonging to the event emitter is unref'd and the no more events will come; + * in this case, free the event emitter using dc_accounts_event_emitter_unref(). + */ +dc_event_t* dc_accounts_get_next_event (dc_accounts_event_emitter_t* emitter); + + +/** + * Free an accounts event emitter object. + * + * @memberof dc_accounts_event_emitter_t + * @param emitter Event emitter object as returned from dc_accounts_get_event_emitter(). + * If NULL is given, nothing is done and an error is logged. + * @return None. + */ +void dc_accounts_event_emitter_unref(dc_accounts_event_emitter_t* emitter); + + /** * @class dc_event_t * @@ -4118,6 +4420,18 @@ int dc_event_get_data2_int(dc_event_t* event); char* dc_event_get_data2_str(dc_event_t* event); +/** + * Get account-id this event belongs to. + * The account-id is of interest only when using the dc_accounts_t account manager. + * To get the context object belonging to the event, use dc_accounts_get_account(). + * + * @memberof dc_event_t + * @param event Event object as returned from dc_accounts_get_next_event(). + * @return account-id belonging to the event or 0 for errors. + */ +uint32_t dc_event_get_account_id(dc_event_t* event); + + /** * Free memory used by an event object. * If you forget to do this for an event, this will result in memory leakage. diff --git a/deltachat-ffi/src/lib.rs b/deltachat-ffi/src/lib.rs index 0a1768c7c..9d5d2a531 100644 --- a/deltachat-ffi/src/lib.rs +++ b/deltachat-ffi/src/lib.rs @@ -24,6 +24,7 @@ use std::time::{Duration, SystemTime}; use async_std::task::{block_on, spawn}; use num_traits::{FromPrimitive, ToPrimitive}; +use deltachat::accounts::Accounts; use deltachat::chat::{ChatId, ChatVisibility, MuteDuration}; use deltachat::constants::DC_MSG_ID_LAST_SPECIAL; use deltachat::contact::{Contact, Origin}; @@ -75,13 +76,17 @@ pub unsafe extern "C" fn dc_context_new( }; let ctx = if blobdir.is_null() || *blobdir == 0 { - block_on(Context::new(os_name, as_path(dbfile).to_path_buf().into())) - } else { - block_on(Context::with_blobdir( + use rand::Rng; + // generate random ID as this functionality is not yet available on the C-api. + let id = rand::thread_rng().gen(); + block_on(Context::new( os_name, as_path(dbfile).to_path_buf().into(), - as_path(blobdir).to_path_buf().into(), + id, )) + } else { + eprintln!("blobdir can not be defined explicitly anymore"); + return ptr::null_mut(); }; match ctx { Ok(ctx) => Box::into_raw(Box::new(ctx)), @@ -301,6 +306,16 @@ pub unsafe extern "C" fn dc_is_io_running(context: *mut dc_context_t) -> libc::c block_on(ctx.is_io_running()) as libc::c_int } +#[no_mangle] +pub unsafe extern "C" fn dc_get_id(context: *mut dc_context_t) -> libc::c_int { + if context.is_null() { + return 0; + } + let ctx = &*context; + + ctx.get_id() as libc::c_int +} + #[no_mangle] pub type dc_event_t = Event; @@ -332,38 +347,38 @@ pub unsafe extern "C" fn dc_event_get_data1_int(event: *mut dc_event_t) -> libc: return 0; } - let event = &*event; + let event = &(*event).typ; match event { - Event::Info(_) - | Event::SmtpConnected(_) - | Event::ImapConnected(_) - | Event::SmtpMessageSent(_) - | Event::ImapMessageDeleted(_) - | Event::ImapMessageMoved(_) - | Event::ImapFolderEmptied(_) - | Event::NewBlobFile(_) - | Event::DeletedBlobFile(_) - | Event::Warning(_) - | Event::Error(_) - | Event::ErrorNetwork(_) - | Event::ErrorSelfNotInGroup(_) => 0, - Event::MsgsChanged { chat_id, .. } - | Event::IncomingMsg { chat_id, .. } - | Event::MsgDelivered { chat_id, .. } - | Event::MsgFailed { chat_id, .. } - | Event::MsgRead { chat_id, .. } - | Event::ChatModified(chat_id) - | Event::ChatEphemeralTimerModified { chat_id, .. } => chat_id.to_u32() as libc::c_int, - Event::ContactsChanged(id) | Event::LocationChanged(id) => { + EventType::Info(_) + | EventType::SmtpConnected(_) + | EventType::ImapConnected(_) + | EventType::SmtpMessageSent(_) + | EventType::ImapMessageDeleted(_) + | EventType::ImapMessageMoved(_) + | EventType::ImapFolderEmptied(_) + | EventType::NewBlobFile(_) + | EventType::DeletedBlobFile(_) + | EventType::Warning(_) + | EventType::Error(_) + | EventType::ErrorNetwork(_) + | EventType::ErrorSelfNotInGroup(_) => 0, + EventType::MsgsChanged { chat_id, .. } + | EventType::IncomingMsg { chat_id, .. } + | EventType::MsgDelivered { chat_id, .. } + | EventType::MsgFailed { chat_id, .. } + | EventType::MsgRead { chat_id, .. } + | EventType::ChatModified(chat_id) + | EventType::ChatEphemeralTimerModified { chat_id, .. } => chat_id.to_u32() as libc::c_int, + EventType::ContactsChanged(id) | EventType::LocationChanged(id) => { let id = id.unwrap_or_default(); id as libc::c_int } - Event::ConfigureProgress(progress) | Event::ImexProgress(progress) => { + EventType::ConfigureProgress(progress) | EventType::ImexProgress(progress) => { *progress as libc::c_int } - Event::ImexFileWritten(_) => 0, - Event::SecurejoinInviterProgress { contact_id, .. } - | Event::SecurejoinJoinerProgress { contact_id, .. } => *contact_id as libc::c_int, + EventType::ImexFileWritten(_) => 0, + EventType::SecurejoinInviterProgress { contact_id, .. } + | EventType::SecurejoinJoinerProgress { contact_id, .. } => *contact_id as libc::c_int, } } @@ -374,36 +389,36 @@ pub unsafe extern "C" fn dc_event_get_data2_int(event: *mut dc_event_t) -> libc: return 0; } - let event = &*event; + let event = &(*event).typ; match event { - Event::Info(_) - | Event::SmtpConnected(_) - | Event::ImapConnected(_) - | Event::SmtpMessageSent(_) - | Event::ImapMessageDeleted(_) - | Event::ImapMessageMoved(_) - | Event::ImapFolderEmptied(_) - | Event::NewBlobFile(_) - | Event::DeletedBlobFile(_) - | Event::Warning(_) - | Event::Error(_) - | Event::ErrorNetwork(_) - | Event::ErrorSelfNotInGroup(_) - | Event::ContactsChanged(_) - | Event::LocationChanged(_) - | Event::ConfigureProgress(_) - | Event::ImexProgress(_) - | Event::ImexFileWritten(_) - | Event::ChatModified(_) => 0, - Event::MsgsChanged { msg_id, .. } - | Event::IncomingMsg { msg_id, .. } - | Event::MsgDelivered { msg_id, .. } - | Event::MsgFailed { msg_id, .. } - | Event::MsgRead { msg_id, .. } => msg_id.to_u32() as libc::c_int, - Event::SecurejoinInviterProgress { progress, .. } - | Event::SecurejoinJoinerProgress { progress, .. } => *progress as libc::c_int, - Event::ChatEphemeralTimerModified { timer, .. } => timer.to_u32() as libc::c_int, + EventType::Info(_) + | EventType::SmtpConnected(_) + | EventType::ImapConnected(_) + | EventType::SmtpMessageSent(_) + | EventType::ImapMessageDeleted(_) + | EventType::ImapMessageMoved(_) + | EventType::ImapFolderEmptied(_) + | EventType::NewBlobFile(_) + | EventType::DeletedBlobFile(_) + | EventType::Warning(_) + | EventType::Error(_) + | EventType::ErrorNetwork(_) + | EventType::ErrorSelfNotInGroup(_) + | EventType::ContactsChanged(_) + | EventType::LocationChanged(_) + | EventType::ConfigureProgress(_) + | EventType::ImexProgress(_) + | EventType::ImexFileWritten(_) + | EventType::ChatModified(_) => 0, + EventType::MsgsChanged { msg_id, .. } + | EventType::IncomingMsg { msg_id, .. } + | EventType::MsgDelivered { msg_id, .. } + | EventType::MsgFailed { msg_id, .. } + | EventType::MsgRead { msg_id, .. } => msg_id.to_u32() as libc::c_int, + EventType::SecurejoinInviterProgress { progress, .. } + | EventType::SecurejoinJoinerProgress { progress, .. } => *progress as libc::c_int, + EventType::ChatEphemeralTimerModified { timer, .. } => timer.to_u32() as libc::c_int, } } @@ -414,45 +429,55 @@ pub unsafe extern "C" fn dc_event_get_data2_str(event: *mut dc_event_t) -> *mut return ptr::null_mut(); } - let event = &*event; + let event = &(*event).typ; match event { - Event::Info(msg) - | Event::SmtpConnected(msg) - | Event::ImapConnected(msg) - | Event::SmtpMessageSent(msg) - | Event::ImapMessageDeleted(msg) - | Event::ImapMessageMoved(msg) - | Event::ImapFolderEmptied(msg) - | Event::NewBlobFile(msg) - | Event::DeletedBlobFile(msg) - | Event::Warning(msg) - | Event::Error(msg) - | Event::ErrorNetwork(msg) - | Event::ErrorSelfNotInGroup(msg) => { + EventType::Info(msg) + | EventType::SmtpConnected(msg) + | EventType::ImapConnected(msg) + | EventType::SmtpMessageSent(msg) + | EventType::ImapMessageDeleted(msg) + | EventType::ImapMessageMoved(msg) + | EventType::ImapFolderEmptied(msg) + | EventType::NewBlobFile(msg) + | EventType::DeletedBlobFile(msg) + | EventType::Warning(msg) + | EventType::Error(msg) + | EventType::ErrorNetwork(msg) + | EventType::ErrorSelfNotInGroup(msg) => { let data2 = msg.to_c_string().unwrap_or_default(); data2.into_raw() } - Event::MsgsChanged { .. } - | Event::IncomingMsg { .. } - | Event::MsgDelivered { .. } - | Event::MsgFailed { .. } - | Event::MsgRead { .. } - | Event::ChatModified(_) - | Event::ContactsChanged(_) - | Event::LocationChanged(_) - | Event::ConfigureProgress(_) - | Event::ImexProgress(_) - | Event::SecurejoinInviterProgress { .. } - | Event::SecurejoinJoinerProgress { .. } - | Event::ChatEphemeralTimerModified { .. } => ptr::null_mut(), - Event::ImexFileWritten(file) => { + EventType::MsgsChanged { .. } + | EventType::IncomingMsg { .. } + | EventType::MsgDelivered { .. } + | EventType::MsgFailed { .. } + | EventType::MsgRead { .. } + | EventType::ChatModified(_) + | EventType::ContactsChanged(_) + | EventType::LocationChanged(_) + | EventType::ConfigureProgress(_) + | EventType::ImexProgress(_) + | EventType::SecurejoinInviterProgress { .. } + | EventType::SecurejoinJoinerProgress { .. } + | EventType::ChatEphemeralTimerModified { .. } => ptr::null_mut(), + EventType::ImexFileWritten(file) => { let data2 = file.to_c_string().unwrap_or_default(); data2.into_raw() } } } +#[no_mangle] +pub unsafe extern "C" fn dc_event_get_account_id(event: *mut dc_event_t) -> u32 { + if event.is_null() { + eprintln!("ignoring careless call to dc_event_get_account_id()"); + return 0; + } + + (*event).id +} + #[no_mangle] pub type dc_event_emitter_t = EventEmitter; @@ -3335,3 +3360,250 @@ pub unsafe extern "C" fn dc_provider_unref(provider: *mut dc_provider_t) { // currently, there is nothing to free, the provider info is a static object. // this may change once we start localizing string. } + +// -- Accounts + +/// Struct representing a list of deltachat accounts. +pub type dc_accounts_t = Accounts; + +#[no_mangle] +pub unsafe extern "C" fn dc_accounts_new( + os_name: *const libc::c_char, + dbfile: *const libc::c_char, +) -> *mut dc_accounts_t { + setup_panic!(); + + if dbfile.is_null() { + eprintln!("ignoring careless call to dc_accounts_new()"); + return ptr::null_mut(); + } + + let os_name = if os_name.is_null() { + String::from("DcFFI") + } else { + to_string_lossy(os_name) + }; + + let accs = block_on(Accounts::new(os_name, as_path(dbfile).to_path_buf().into())); + + match accs { + Ok(accs) => Box::into_raw(Box::new(accs)), + Err(err) => { + eprintln!("failed to create accounts: {}", err); + ptr::null_mut() + } + } +} + +/// Release the accounts structure. +/// +/// This function releases the memory of the `dc_accounts_t` structure. +#[no_mangle] +pub unsafe extern "C" fn dc_accounts_unref(accounts: *mut dc_accounts_t) { + if accounts.is_null() { + eprintln!("ignoring careless call to dc_accounts_unref()"); + return; + } + let _ = Box::from_raw(accounts); +} + +#[no_mangle] +pub unsafe extern "C" fn dc_accounts_get_account( + accounts: *mut dc_accounts_t, + id: u32, +) -> *mut dc_context_t { + if accounts.is_null() { + eprintln!("ignoring careless call to dc_accounts_get_account()"); + return ptr::null_mut(); + } + + let accounts = &*accounts; + block_on(accounts.get_account(id)) + .map(|ctx| Box::into_raw(Box::new(ctx))) + .unwrap_or_else(std::ptr::null_mut) +} + +#[no_mangle] +pub unsafe extern "C" fn dc_accounts_get_selected_account( + accounts: *mut dc_accounts_t, +) -> *mut dc_context_t { + if accounts.is_null() { + eprintln!("ignoring careless call to dc_accounts_get_selected_account()"); + return ptr::null_mut(); + } + + let accounts = &*accounts; + let ctx = block_on(accounts.get_selected_account()); + Box::into_raw(Box::new(ctx)) +} + +#[no_mangle] +pub unsafe extern "C" fn dc_accounts_select_account( + accounts: *mut dc_accounts_t, + id: u32, +) -> libc::c_int { + if accounts.is_null() { + eprintln!("ignoring careless call to dc_accounts_select_account()"); + return 0; + } + + let accounts = &*accounts; + block_on(accounts.select_account(id)) + .map(|_| 1) + .unwrap_or_else(|_| 0) +} + +#[no_mangle] +pub unsafe extern "C" fn dc_accounts_add_account(accounts: *mut dc_accounts_t) -> u32 { + if accounts.is_null() { + eprintln!("ignoring careless call to dc_accounts_add_account()"); + return 0; + } + + let accounts = &*accounts; + + block_on(accounts.add_account()).unwrap_or_else(|_| 0) +} + +#[no_mangle] +pub unsafe extern "C" fn dc_accounts_remove_account( + accounts: *mut dc_accounts_t, + id: u32, +) -> libc::c_int { + if accounts.is_null() { + eprintln!("ignoring careless call to dc_accounts_remove_account()"); + return 0; + } + + let accounts = &*accounts; + + block_on(accounts.remove_account(id)) + .map(|_| 1) + .unwrap_or_else(|_| 0) +} + +#[no_mangle] +pub unsafe extern "C" fn dc_accounts_migrate_account( + accounts: *mut dc_accounts_t, + dbfile: *const libc::c_char, +) -> u32 { + if accounts.is_null() || dbfile.is_null() { + eprintln!("ignoring careless call to dc_accounts_migrate_account()"); + return 0; + } + + let accounts = &*accounts; + let dbfile = to_string_lossy(dbfile); + + block_on(accounts.migrate_account(async_std::path::PathBuf::from(dbfile))) + .map(|_| 1) + .unwrap_or_else(|_| 0) +} + +#[no_mangle] +pub unsafe extern "C" fn dc_accounts_get_all(accounts: *mut dc_accounts_t) -> *mut dc_array_t { + if accounts.is_null() { + eprintln!("ignoring careless call to dc_accounts_get_all()"); + return ptr::null_mut(); + } + + let accounts = &*accounts; + let list = block_on(accounts.get_all()); + let array: dc_array_t = list.into(); + + Box::into_raw(Box::new(array)) +} + +#[no_mangle] +pub unsafe extern "C" fn dc_accounts_import_account( + accounts: *mut dc_accounts_t, + file: *const libc::c_char, +) -> u32 { + if accounts.is_null() || file.is_null() { + eprintln!("ignoring careless call to dc_accounts_import_account()"); + return 0; + } + + let accounts = &*accounts; + let file = to_string_lossy(file); + block_on(accounts.import_account(async_std::path::PathBuf::from(file))) + .map(|_| 1) + .unwrap_or_else(|_| 0) +} + +#[no_mangle] +pub unsafe extern "C" fn dc_accounts_start_io(accounts: *mut dc_accounts_t) { + if accounts.is_null() { + eprintln!("ignoring careless call to dc_accounts_start_io()"); + return; + } + + let accounts = &*accounts; + block_on(accounts.start_io()); +} + +#[no_mangle] +pub unsafe extern "C" fn dc_accounts_stop_io(accounts: *mut dc_accounts_t) { + if accounts.is_null() { + eprintln!("ignoring careless call to dc_accounts_stop_io()"); + return; + } + + let accounts = &*accounts; + block_on(accounts.stop_io()); +} + +#[no_mangle] +pub unsafe extern "C" fn dc_accounts_maybe_network(accounts: *mut dc_accounts_t) { + if accounts.is_null() { + eprintln!("ignoring careless call to dc_accounts_mabye_network()"); + return; + } + + let accounts = &*accounts; + block_on(accounts.maybe_network()); +} + +#[no_mangle] +pub type dc_accounts_event_emitter_t = deltachat::accounts::EventEmitter; + +#[no_mangle] +pub unsafe extern "C" fn dc_accounts_get_event_emitter( + accounts: *mut dc_accounts_t, +) -> *mut dc_accounts_event_emitter_t { + if accounts.is_null() { + eprintln!("ignoring careless call to dc_accounts_get_event_emitter()"); + return ptr::null_mut(); + } + + let accounts = &*accounts; + let emitter = block_on(accounts.get_event_emitter()); + + Box::into_raw(Box::new(emitter)) +} + +#[no_mangle] +pub unsafe extern "C" fn dc_accounts_event_emitter_unref( + emitter: *mut dc_accounts_event_emitter_t, +) { + if emitter.is_null() { + eprintln!("ignoring careless call to dc_accounts_event_emitter_unref()"); + return; + } + let _ = Box::from_raw(emitter); +} + +#[no_mangle] +pub unsafe extern "C" fn dc_accounts_get_next_event( + emitter: *mut dc_accounts_event_emitter_t, +) -> *mut dc_event_t { + if emitter.is_null() { + return ptr::null_mut(); + } + let emitter = &*emitter; + + emitter + .recv_sync() + .map(|ev| Box::into_raw(Box::new(ev))) + .unwrap_or_else(ptr::null_mut) +} diff --git a/examples/repl/cmdline.rs b/examples/repl/cmdline.rs index 8df9358a9..58cf4582f 100644 --- a/examples/repl/cmdline.rs +++ b/examples/repl/cmdline.rs @@ -17,7 +17,7 @@ use deltachat::message::{self, Message, MessageState, MsgId}; use deltachat::peerstate::*; use deltachat::qr::*; use deltachat::sql; -use deltachat::Event; +use deltachat::EventType; use deltachat::{config, provider}; /// Reset database tables. @@ -86,7 +86,7 @@ async fn reset_tables(context: &Context, bits: i32) { println!("(8) Rest but server config reset."); } - context.emit_event(Event::MsgsChanged { + context.emit_event(EventType::MsgsChanged { chat_id: ChatId::new(0), msg_id: MsgId::new(0), }); @@ -157,7 +157,7 @@ async fn poke_spec(context: &Context, spec: Option<&str>) -> bool { } println!("Import: {} items read from \"{}\".", read_cnt, &real_spec); if read_cnt > 0 { - context.emit_event(Event::MsgsChanged { + context.emit_event(EventType::MsgsChanged { chat_id: ChatId::new(0), msg_id: MsgId::new(0), }); @@ -1059,7 +1059,7 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu // "event" => { // ensure!(!arg1.is_empty(), "Argument missing."); // let event = arg1.parse()?; - // let event = Event::from_u32(event).ok_or(format_err!("Event::from_u32({})", event))?; + // let event = EventType::from_u32(event).ok_or(format_err!("EventType::from_u32({})", event))?; // let r = context.emit_event(event, 0 as libc::uintptr_t, 0 as libc::uintptr_t); // println!( // "Sending event {:?}({}), received value {}.", diff --git a/examples/repl/main.rs b/examples/repl/main.rs index 7be1c9bea..1cddc9266 100644 --- a/examples/repl/main.rs +++ b/examples/repl/main.rs @@ -19,7 +19,7 @@ use deltachat::config; use deltachat::context::*; use deltachat::oauth2::*; use deltachat::securejoin::*; -use deltachat::Event; +use deltachat::EventType; use log::{error, info, warn}; use rustyline::completion::{Completer, FilenameCompleter, Pair}; use rustyline::config::OutputStreamType; @@ -34,35 +34,35 @@ mod cmdline; use self::cmdline::*; /// Event Handler -fn receive_event(event: Event) { +fn receive_event(event: EventType) { let yellow = Color::Yellow.normal(); match event { - Event::Info(msg) => { + EventType::Info(msg) => { /* do not show the event as this would fill the screen */ info!("{}", msg); } - Event::SmtpConnected(msg) => { + EventType::SmtpConnected(msg) => { info!("[SMTP_CONNECTED] {}", msg); } - Event::ImapConnected(msg) => { + EventType::ImapConnected(msg) => { info!("[IMAP_CONNECTED] {}", msg); } - Event::SmtpMessageSent(msg) => { + EventType::SmtpMessageSent(msg) => { info!("[SMTP_MESSAGE_SENT] {}", msg); } - Event::Warning(msg) => { + EventType::Warning(msg) => { warn!("{}", msg); } - Event::Error(msg) => { + EventType::Error(msg) => { error!("{}", msg); } - Event::ErrorNetwork(msg) => { + EventType::ErrorNetwork(msg) => { error!("[NETWORK] msg={}", msg); } - Event::ErrorSelfNotInGroup(msg) => { + EventType::ErrorSelfNotInGroup(msg) => { error!("[SELF_NOT_IN_GROUP] {}", msg); } - Event::MsgsChanged { chat_id, msg_id } => { + EventType::MsgsChanged { chat_id, msg_id } => { info!( "{}", yellow.paint(format!( @@ -71,34 +71,34 @@ fn receive_event(event: Event) { )) ); } - Event::ContactsChanged(_) => { + EventType::ContactsChanged(_) => { info!("{}", yellow.paint("Received CONTACTS_CHANGED()")); } - Event::LocationChanged(contact) => { + EventType::LocationChanged(contact) => { info!( "{}", yellow.paint(format!("Received LOCATION_CHANGED(contact={:?})", contact)) ); } - Event::ConfigureProgress(progress) => { + EventType::ConfigureProgress(progress) => { info!( "{}", yellow.paint(format!("Received CONFIGURE_PROGRESS({} ‰)", progress)) ); } - Event::ImexProgress(progress) => { + EventType::ImexProgress(progress) => { info!( "{}", yellow.paint(format!("Received IMEX_PROGRESS({} ‰)", progress)) ); } - Event::ImexFileWritten(file) => { + EventType::ImexFileWritten(file) => { info!( "{}", yellow.paint(format!("Received IMEX_FILE_WRITTEN({})", file.display())) ); } - Event::ChatModified(chat) => { + EventType::ChatModified(chat) => { info!( "{}", yellow.paint(format!("Received CHAT_MODIFIED({})", chat)) @@ -272,12 +272,12 @@ async fn start(args: Vec) -> Result<(), Error> { println!("Error: Bad arguments, expected [db-name]."); bail!("No db-name specified"); } - let context = Context::new("CLI".into(), Path::new(&args[1]).to_path_buf()).await?; + let context = Context::new("CLI".into(), Path::new(&args[1]).to_path_buf(), 0).await?; let events = context.get_event_emitter(); async_std::task::spawn(async move { while let Some(event) = events.recv().await { - receive_event(event); + receive_event(event.typ); } }); diff --git a/examples/simple.rs b/examples/simple.rs index 2d37769ad..863960ea3 100644 --- a/examples/simple.rs +++ b/examples/simple.rs @@ -6,20 +6,20 @@ use deltachat::config; use deltachat::contact::*; use deltachat::context::*; use deltachat::message::Message; -use deltachat::Event; +use deltachat::EventType; -fn cb(event: Event) { +fn cb(event: EventType) { match event { - Event::ConfigureProgress(progress) => { + EventType::ConfigureProgress(progress) => { log::info!("progress: {}", progress); } - Event::Info(msg) => { + EventType::Info(msg) => { log::info!("{}", msg); } - Event::Warning(msg) => { + EventType::Warning(msg) => { log::warn!("{}", msg); } - Event::Error(msg) | Event::ErrorNetwork(msg) => { + EventType::Error(msg) | EventType::ErrorNetwork(msg) => { log::error!("{}", msg); } event => { @@ -36,7 +36,7 @@ async fn main() { let dir = tempdir().unwrap(); let dbfile = dir.path().join("db.sqlite"); log::info!("creating database {:?}", dbfile); - let ctx = Context::new("FakeOs".into(), dbfile.into()) + let ctx = Context::new("FakeOs".into(), dbfile.into(), 0) .await .expect("Failed to create context"); let info = ctx.get_info().await; @@ -45,7 +45,7 @@ async fn main() { let events = ctx.get_event_emitter(); let events_spawn = async_std::task::spawn(async move { while let Some(event) = events.recv().await { - cb(event); + cb(event.typ); } }); diff --git a/src/accounts.rs b/src/accounts.rs new file mode 100644 index 000000000..025c41144 --- /dev/null +++ b/src/accounts.rs @@ -0,0 +1,533 @@ +use std::collections::HashMap; +use std::pin::Pin; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::task::{Context as TaskContext, Poll}; + +use async_std::fs; +use async_std::path::PathBuf; +use async_std::sync::{Arc, RwLock}; +use uuid::Uuid; + +use anyhow::{ensure, Context as _}; +use serde::{Deserialize, Serialize}; + +use crate::context::Context; +use crate::error::Result; +use crate::events::Event; + +/// Account manager, that can handle multiple accounts in a single place. +#[derive(Debug, Clone)] +pub struct Accounts { + dir: PathBuf, + config: Config, + accounts: Arc>>, +} + +impl Accounts { + /// Loads or creates an accounts folder at the given `dir`. + pub async fn new(os_name: String, dir: PathBuf) -> Result { + if !dir.exists().await { + Accounts::create(os_name, &dir).await?; + } + + Accounts::open(dir).await + } + + /// Creates a new default structure, including a default account. + pub async fn create(os_name: String, dir: &PathBuf) -> Result<()> { + fs::create_dir_all(dir) + .await + .context("failed to create folder")?; + + // create default account + let config = Config::new(os_name.clone(), dir).await?; + let account_config = config.new_account(dir).await?; + + Context::new(os_name, account_config.dbfile().into(), account_config.id) + .await + .context("failed to create default account")?; + + Ok(()) + } + + /// Opens an existing accounts structure. Will error if the folder doesn't exist, + /// no account exists and no config exists. + pub async fn open(dir: PathBuf) -> Result { + ensure!(dir.exists().await, "directory does not exist"); + + let config_file = dir.join(CONFIG_NAME); + ensure!(config_file.exists().await, "accounts.toml does not exist"); + + let config = Config::from_file(config_file).await?; + let accounts = config.load_accounts().await?; + + Ok(Self { + dir, + config, + accounts: Arc::new(RwLock::new(accounts)), + }) + } + + /// Get an account by its `id`: + pub async fn get_account(&self, id: u32) -> Option { + self.accounts.read().await.get(&id).cloned() + } + + /// Get the currently selected account. + pub async fn get_selected_account(&self) -> Context { + let id = self.config.get_selected_account().await; + self.accounts + .read() + .await + .get(&id) + .cloned() + .expect("inconsistent state") + } + + /// Select the given account. + pub async fn select_account(&self, id: u32) -> Result<()> { + self.config.select_account(id).await?; + + Ok(()) + } + + /// Add a new account. + pub async fn add_account(&self) -> Result { + let os_name = self.config.os_name().await; + let account_config = self.config.new_account(&self.dir).await?; + + let ctx = Context::new(os_name, account_config.dbfile().into(), account_config.id).await?; + self.accounts.write().await.insert(account_config.id, ctx); + + Ok(account_config.id) + } + + /// Remove an account. + pub async fn remove_account(&self, id: u32) -> Result<()> { + let ctx = self.accounts.write().await.remove(&id); + ensure!(ctx.is_some(), "no account with this id: {}", id); + let ctx = ctx.unwrap(); + ctx.stop_io().await; + drop(ctx); + + if let Some(cfg) = self.config.get_account(id).await { + fs::remove_dir_all(async_std::path::PathBuf::from(&cfg.dir)) + .await + .context("failed to remove account data")?; + } + self.config.remove_account(id).await?; + + Ok(()) + } + + /// Migrate an existing account into this structure. + pub async fn migrate_account(&self, dbfile: PathBuf) -> Result { + let blobdir = Context::derive_blobdir(&dbfile); + + ensure!( + dbfile.exists().await, + "no database found: {}", + dbfile.display() + ); + ensure!( + blobdir.exists().await, + "no blobdir found: {}", + blobdir.display() + ); + + let old_id = self.config.get_selected_account().await; + + // create new account + let account_config = self.config.new_account(&self.dir).await?; + + let new_dbfile = account_config.dbfile().into(); + let new_blobdir = Context::derive_blobdir(&new_dbfile); + + let res = { + fs::create_dir_all(&account_config.dir).await?; + fs::rename(&dbfile, &new_dbfile).await?; + fs::rename(&blobdir, &new_blobdir).await?; + Ok(()) + }; + + match res { + Ok(_) => { + let ctx = Context::with_blobdir( + self.config.os_name().await, + new_dbfile, + new_blobdir, + account_config.id, + ) + .await?; + self.accounts.write().await.insert(account_config.id, ctx); + Ok(account_config.id) + } + Err(err) => { + // remove temp account + fs::remove_dir_all(async_std::path::PathBuf::from(&account_config.dir)) + .await + .context("failed to remove account data")?; + + self.config.remove_account(account_config.id).await?; + + // set selection back + self.select_account(old_id).await?; + + Err(err) + } + } + } + + /// Get a list of all account ids. + pub async fn get_all(&self) -> Vec { + self.accounts.read().await.keys().copied().collect() + } + + /// Import a backup using a new account and selects it. + pub async fn import_account(&self, file: PathBuf) -> Result { + let old_id = self.config.get_selected_account().await; + + let id = self.add_account().await?; + let ctx = self.get_account(id).await.expect("just added"); + + match crate::imex::imex(&ctx, crate::imex::ImexMode::ImportBackup, Some(file)).await { + Ok(_) => Ok(id), + Err(err) => { + // remove temp account + self.remove_account(id).await?; + // set selection back + self.select_account(old_id).await?; + Err(err) + } + } + } + + pub async fn start_io(&self) { + let accounts = &*self.accounts.read().await; + for account in accounts.values() { + account.start_io().await; + } + } + + pub async fn stop_io(&self) { + let accounts = &*self.accounts.read().await; + for account in accounts.values() { + account.stop_io().await; + } + } + + pub async fn maybe_network(&self) { + let accounts = &*self.accounts.read().await; + for account in accounts.values() { + account.maybe_network().await; + } + } + + /// Unified event emitter. + pub async fn get_event_emitter(&self) -> EventEmitter { + let emitters = self + .accounts + .read() + .await + .iter() + .map(|(id, a)| EmitterWrapper { + id: *id, + emitter: a.get_event_emitter(), + done: AtomicBool::new(false), + }) + .collect(); + + EventEmitter(emitters) + } +} + +impl EventEmitter { + /// Blocking recv of an event. Return `None` if the `Sender` has been droped. + pub fn recv_sync(&self) -> Option { + async_std::task::block_on(self.recv()) + } + + /// Async recv of an event. Return `None` if the `Sender` has been droped. + pub async fn recv(&self) -> Option { + futures::future::poll_fn(|cx| Pin::new(self).recv_poll(cx)).await + } + + fn recv_poll(self: Pin<&Self>, _cx: &mut TaskContext<'_>) -> Poll> { + for e in &*self.0 { + if e.done.load(Ordering::Acquire) { + // skip emitters that are already done + continue; + } + + match e.emitter.try_recv() { + Ok(event) => return Poll::Ready(Some(event)), + Err(async_std::sync::TryRecvError::Disconnected) => { + e.done.store(true, Ordering::Release); + } + Err(async_std::sync::TryRecvError::Empty) => {} + } + } + + Poll::Pending + } +} + +#[derive(Debug)] +pub struct EventEmitter(Vec); + +#[derive(Debug)] +struct EmitterWrapper { + id: u32, + emitter: crate::events::EventEmitter, + done: AtomicBool, +} + +pub const CONFIG_NAME: &str = "accounts.toml"; +pub const DB_NAME: &str = "dc.db"; + +#[derive(Debug, Clone)] +pub struct Config { + file: PathBuf, + inner: Arc>, +} + +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] +struct InnerConfig { + pub os_name: String, + /// The currently selected account. + pub selected_account: u32, + pub next_id: u32, + pub accounts: Vec, +} + +impl Config { + pub async fn new(os_name: String, dir: &PathBuf) -> Result { + let cfg = Config { + file: dir.join(CONFIG_NAME), + inner: Arc::new(RwLock::new(InnerConfig { + os_name, + accounts: Vec::new(), + selected_account: 0, + next_id: 1, + })), + }; + + cfg.sync().await?; + + Ok(cfg) + } + + pub async fn os_name(&self) -> String { + self.inner.read().await.os_name.clone() + } + + /// Sync the inmemory representation to disk. + async fn sync(&self) -> Result<()> { + fs::write( + &self.file, + toml::to_string_pretty(&*self.inner.read().await)?, + ) + .await + .context("failed to write config") + } + + /// Read a configuration from the given file into memory. + pub async fn from_file(file: PathBuf) -> Result { + let bytes = fs::read(&file).await.context("failed to read file")?; + let inner: InnerConfig = toml::from_slice(&bytes).context("failed to parse config")?; + + Ok(Config { + file, + inner: Arc::new(RwLock::new(inner)), + }) + } + + pub async fn load_accounts(&self) -> Result> { + let cfg = &*self.inner.read().await; + let mut accounts = HashMap::with_capacity(cfg.accounts.len()); + for account_config in &cfg.accounts { + let ctx = Context::new( + cfg.os_name.clone(), + account_config.dbfile().into(), + account_config.id, + ) + .await?; + accounts.insert(account_config.id, ctx); + } + + Ok(accounts) + } + + /// Create a new account in the given root directory. + pub async fn new_account(&self, dir: &PathBuf) -> Result { + let id = { + let inner = &mut self.inner.write().await; + let id = inner.next_id; + let uuid = Uuid::new_v4(); + let target_dir = dir.join(uuid.to_simple_ref().to_string()); + + inner.accounts.push(AccountConfig { + id, + name: String::new(), + dir: target_dir.into(), + uuid, + }); + inner.next_id += 1; + id + }; + + self.sync().await?; + + self.select_account(id).await.expect("just added"); + let cfg = self.get_account(id).await.expect("just added"); + Ok(cfg) + } + + /// Removes an existing acccount entirely. + pub async fn remove_account(&self, id: u32) -> Result<()> { + { + let inner = &mut *self.inner.write().await; + if let Some(idx) = inner.accounts.iter().position(|e| e.id == id) { + // remove account from the configs + inner.accounts.remove(idx); + } + if inner.selected_account == id { + // reset selected account + inner.selected_account = inner.accounts.get(0).map(|e| e.id).unwrap_or_default(); + } + } + + self.sync().await + } + + pub async fn get_account(&self, id: u32) -> Option { + self.inner + .read() + .await + .accounts + .iter() + .find(|e| e.id == id) + .cloned() + } + + pub async fn get_selected_account(&self) -> u32 { + self.inner.read().await.selected_account + } + + pub async fn select_account(&self, id: u32) -> Result<()> { + { + let inner = &mut *self.inner.write().await; + ensure!( + inner.accounts.iter().any(|e| e.id == id), + "invalid account id: {}", + id + ); + + inner.selected_account = id; + } + + self.sync().await?; + Ok(()) + } +} + +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] +pub struct AccountConfig { + /// Unique id. + pub id: u32, + /// Display name + pub name: String, + /// Root directory for all data for this account. + pub dir: std::path::PathBuf, + pub uuid: Uuid, +} + +impl AccountConfig { + /// Get the canoncial dbfile name for this configuration. + pub fn dbfile(&self) -> std::path::PathBuf { + self.dir.join(DB_NAME) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[async_std::test] + async fn test_account_new_open() { + let dir = tempfile::tempdir().unwrap(); + let p: PathBuf = dir.path().join("accounts1").into(); + + let accounts1 = Accounts::new("my_os".into(), p.clone()).await.unwrap(); + let accounts2 = Accounts::open(p).await.unwrap(); + + assert_eq!(accounts1.accounts.read().await.len(), 1); + assert_eq!(accounts1.config.get_selected_account().await, 1); + + assert_eq!(accounts1.dir, accounts2.dir); + assert_eq!( + &*accounts1.config.inner.read().await, + &*accounts2.config.inner.read().await, + ); + assert_eq!( + accounts1.accounts.read().await.len(), + accounts2.accounts.read().await.len() + ); + } + + #[async_std::test] + async fn test_account_new_add_remove() { + let dir = tempfile::tempdir().unwrap(); + let p: PathBuf = dir.path().join("accounts").into(); + + let accounts = Accounts::new("my_os".into(), p.clone()).await.unwrap(); + + assert_eq!(accounts.accounts.read().await.len(), 1); + assert_eq!(accounts.config.get_selected_account().await, 1); + + let id = accounts.add_account().await.unwrap(); + assert_eq!(id, 2); + assert_eq!(accounts.config.get_selected_account().await, id); + assert_eq!(accounts.accounts.read().await.len(), 2); + + accounts.select_account(1).await.unwrap(); + assert_eq!(accounts.config.get_selected_account().await, 1); + + accounts.remove_account(1).await.unwrap(); + assert_eq!(accounts.config.get_selected_account().await, 2); + assert_eq!(accounts.accounts.read().await.len(), 1); + } + + #[async_std::test] + async fn test_migrate_account() { + let dir = tempfile::tempdir().unwrap(); + let p: PathBuf = dir.path().join("accounts").into(); + + let accounts = Accounts::new("my_os".into(), p.clone()).await.unwrap(); + assert_eq!(accounts.accounts.read().await.len(), 1); + assert_eq!(accounts.config.get_selected_account().await, 1); + + let extern_dbfile: PathBuf = dir.path().join("other").into(); + let ctx = Context::new("my_os".into(), extern_dbfile.clone(), 0) + .await + .unwrap(); + ctx.set_config(crate::config::Config::Addr, Some("me@mail.com")) + .await + .unwrap(); + + drop(ctx); + + accounts + .migrate_account(extern_dbfile.clone()) + .await + .unwrap(); + assert_eq!(accounts.accounts.read().await.len(), 2); + assert_eq!(accounts.config.get_selected_account().await, 2); + + let ctx = accounts.get_selected_account().await; + assert_eq!( + "me@mail.com", + ctx.get_config(crate::config::Config::Addr).await.unwrap() + ); + } +} diff --git a/src/blob.rs b/src/blob.rs index a6efe141b..3fc3028c0 100644 --- a/src/blob.rs +++ b/src/blob.rs @@ -15,7 +15,7 @@ use crate::config::Config; use crate::constants::*; use crate::context::Context; use crate::error::Error; -use crate::events::Event; +use crate::events::EventType; use crate::message; /// Represents a file in the blob directory. @@ -67,7 +67,7 @@ impl<'a> BlobObject<'a> { blobdir, name: format!("$BLOBDIR/{}", name), }; - context.emit_event(Event::NewBlobFile(blob.as_name().to_string())); + context.emit_event(EventType::NewBlobFile(blob.as_name().to_string())); Ok(blob) } @@ -155,7 +155,7 @@ impl<'a> BlobObject<'a> { blobdir: context.get_blobdir(), name: format!("$BLOBDIR/{}", name), }; - context.emit_event(Event::NewBlobFile(blob.as_name().to_string())); + context.emit_event(EventType::NewBlobFile(blob.as_name().to_string())); Ok(blob) } diff --git a/src/chat.rs b/src/chat.rs index 74af3f678..886a0aa60 100644 --- a/src/chat.rs +++ b/src/chat.rs @@ -17,7 +17,7 @@ use crate::context::Context; use crate::dc_tools::*; use crate::ephemeral::{delete_expired_messages, schedule_ephemeral_task, Timer as EphemeralTimer}; use crate::error::{bail, ensure, format_err, Error}; -use crate::events::Event; +use crate::events::EventType; use crate::job::{self, Action}; use crate::message::{self, InvalidMsgId, Message, MessageState, MsgId}; use crate::mimeparser::SystemMessage; @@ -185,7 +185,7 @@ impl ChatId { ) .await?; - context.emit_event(Event::MsgsChanged { + context.emit_event(EventType::MsgsChanged { msg_id: MsgId::new(0), chat_id: ChatId::new(0), }); @@ -242,7 +242,7 @@ impl ChatId { .execute("DELETE FROM chats WHERE id=?;", paramsv![self]) .await?; - context.emit_event(Event::MsgsChanged { + context.emit_event(EventType::MsgsChanged { msg_id: MsgId::new(0), chat_id: ChatId::new(0), }); @@ -268,7 +268,7 @@ impl ChatId { }; if changed { - context.emit_event(Event::MsgsChanged { + context.emit_event(EventType::MsgsChanged { chat_id: self, msg_id: MsgId::new(0), }); @@ -789,7 +789,7 @@ impl Chat { { emit_event!( context, - Event::ErrorSelfNotInGroup("Cannot send message; self not in group.".into()) + EventType::ErrorSelfNotInGroup("Cannot send message; self not in group.".into()) ); bail!("Cannot set message; self not in group."); } @@ -1146,7 +1146,7 @@ pub async fn create_by_msg_id(context: &Context, msg_id: MsgId) -> Result Result< } }; - context.emit_event(Event::MsgsChanged { + context.emit_event(EventType::MsgsChanged { chat_id: ChatId::new(0), msg_id: MsgId::new(0), }); @@ -1355,7 +1355,7 @@ pub async fn prepare_msg( msg.state = MessageState::OutPreparing; let msg_id = prepare_msg_common(context, chat_id, msg).await?; - context.emit_event(Event::MsgsChanged { + context.emit_event(EventType::MsgsChanged { chat_id: msg.chat_id, msg_id: msg.id, }); @@ -1530,7 +1530,7 @@ pub async fn send_msg_sync( match status { job::Status::Finished(Ok(_)) => { - context.emit_event(Event::MsgsChanged { + context.emit_event(EventType::MsgsChanged { chat_id: msg.chat_id, msg_id: msg.id, }); @@ -1558,13 +1558,13 @@ async fn send_msg_inner( if let Some(send_job) = prepare_send_msg(context, chat_id, msg).await? { job::add(context, send_job).await; - context.emit_event(Event::MsgsChanged { + context.emit_event(EventType::MsgsChanged { chat_id: msg.chat_id, msg_id: msg.id, }); if msg.param.exists(Param::SetLatitude) { - context.emit_event(Event::LocationChanged(Some(DC_CONTACT_ID_SELF))); + context.emit_event(EventType::LocationChanged(Some(DC_CONTACT_ID_SELF))); } } @@ -1665,7 +1665,7 @@ pub async fn get_chat_msgs( // On desktop chatlist is always shown on the side, // and it is important to update the last message shown // there. - context.emit_event(Event::MsgsChanged { + context.emit_event(EventType::MsgsChanged { msg_id: MsgId::new(0), chat_id: ChatId::new(0), }) @@ -1788,7 +1788,7 @@ pub async fn marknoticed_chat(context: &Context, chat_id: ChatId) -> Result<(), ) .await?; - context.emit_event(Event::MsgsChanged { + context.emit_event(EventType::MsgsChanged { chat_id: ChatId::new(0), msg_id: MsgId::new(0), }); @@ -1820,7 +1820,7 @@ pub async fn marknoticed_all_chats(context: &Context) -> Result<(), Error> { ) .await?; - context.emit_event(Event::MsgsChanged { + context.emit_event(EventType::MsgsChanged { msg_id: MsgId::new(0), chat_id: ChatId::new(0), }); @@ -1991,7 +1991,7 @@ pub async fn create_group_chat( chat_id.set_draft_raw(context, &mut draft_msg).await; } - context.emit_event(Event::MsgsChanged { + context.emit_event(EventType::MsgsChanged { msg_id: MsgId::new(0), chat_id: ChatId::new(0), }); @@ -2091,7 +2091,9 @@ pub(crate) async fn add_contact_to_chat_ex( /* we should respect this - whatever we send to the group, it gets discarded anyway! */ emit_event!( context, - Event::ErrorSelfNotInGroup("Cannot add contact to group; self not in group.".into()) + EventType::ErrorSelfNotInGroup( + "Cannot add contact to group; self not in group.".into() + ) ); bail!("can not add contact because our account is not part of it"); } @@ -2149,7 +2151,7 @@ pub(crate) async fn add_contact_to_chat_ex( msg.param.set_int(Param::Arg2, from_handshake.into()); msg.id = send_msg(context, chat_id, &mut msg).await?; } - context.emit_event(Event::ChatModified(chat_id)); + context.emit_event(EventType::ChatModified(chat_id)); Ok(true) } @@ -2311,7 +2313,7 @@ pub async fn set_muted( .await .is_ok() { - context.emit_event(Event::ChatModified(chat_id)); + context.emit_event(EventType::ChatModified(chat_id)); } else { bail!("Failed to set mute duration, chat might not exist -"); } @@ -2343,7 +2345,7 @@ pub async fn remove_contact_from_chat( if !is_contact_in_chat(context, chat_id, DC_CONTACT_ID_SELF).await { emit_event!( context, - Event::ErrorSelfNotInGroup( + EventType::ErrorSelfNotInGroup( "Cannot remove contact from chat; self not in group.".into() ) ); @@ -2391,7 +2393,7 @@ pub async fn remove_contact_from_chat( // removed it first, it would complicate the // check/encryption logic. success = remove_from_chat_contacts_table(context, chat_id, contact_id).await; - context.emit_event(Event::ChatModified(chat_id)); + context.emit_event(EventType::ChatModified(chat_id)); } } } @@ -2451,7 +2453,7 @@ pub async fn set_chat_name( } else if !is_contact_in_chat(context, chat_id, DC_CONTACT_ID_SELF).await { emit_event!( context, - Event::ErrorSelfNotInGroup("Cannot set chat name; self not in group".into()) + EventType::ErrorSelfNotInGroup("Cannot set chat name; self not in group".into()) ); } else { /* we should respect this - whatever we send to the group, it gets discarded anyway! */ @@ -2481,12 +2483,12 @@ pub async fn set_chat_name( msg.param.set(Param::Arg, &chat.name); } msg.id = send_msg(context, chat_id, &mut msg).await?; - context.emit_event(Event::MsgsChanged { + context.emit_event(EventType::MsgsChanged { chat_id, msg_id: msg.id, }); } - context.emit_event(Event::ChatModified(chat_id)); + context.emit_event(EventType::ChatModified(chat_id)); success = true; } } @@ -2519,7 +2521,9 @@ pub async fn set_chat_profile_image( if !is_contact_in_chat(context, chat_id, DC_CONTACT_ID_SELF).await { emit_event!( context, - Event::ErrorSelfNotInGroup("Cannot set chat profile image; self not in group.".into()) + EventType::ErrorSelfNotInGroup( + "Cannot set chat profile image; self not in group.".into() + ) ); bail!("Failed to set profile image"); } @@ -2558,13 +2562,13 @@ pub async fn set_chat_profile_image( msg.id = send_msg(context, chat_id, &mut msg).await?; emit_event!( context, - Event::MsgsChanged { + EventType::MsgsChanged { chat_id, msg_id: msg.id } ); } - emit_event!(context, Event::ChatModified(chat_id)); + emit_event!(context, EventType::ChatModified(chat_id)); Ok(()) } @@ -2648,7 +2652,7 @@ pub async fn forward_msgs( } } for (chat_id, msg_id) in created_chats.iter().zip(created_msgs.iter()) { - context.emit_event(Event::MsgsChanged { + context.emit_event(EventType::MsgsChanged { chat_id: *chat_id, msg_id: *msg_id, }); @@ -2774,9 +2778,9 @@ pub async fn add_device_msg_with_importance( if !msg_id.is_unset() { if important { - context.emit_event(Event::IncomingMsg { chat_id, msg_id }); + context.emit_event(EventType::IncomingMsg { chat_id, msg_id }); } else { - context.emit_event(Event::MsgsChanged { chat_id, msg_id }); + context.emit_event(EventType::MsgsChanged { chat_id, msg_id }); } } @@ -2864,7 +2868,7 @@ pub(crate) async fn add_info_msg(context: &Context, chat_id: ChatId, text: impl .get_rowid(context, "msgs", "rfc724_mid", &rfc724_mid) .await .unwrap_or_default(); - context.emit_event(Event::MsgsChanged { + context.emit_event(EventType::MsgsChanged { chat_id, msg_id: MsgId::new(row_id), }); diff --git a/src/config.rs b/src/config.rs index 6434f560b..4b9524e50 100644 --- a/src/config.rs +++ b/src/config.rs @@ -8,7 +8,7 @@ use crate::chat::ChatId; use crate::constants::DC_VERSION_STR; use crate::context::Context; use crate::dc_tools::*; -use crate::events::Event; +use crate::events::EventType; use crate::message::MsgId; use crate::mimefactory::RECOMMENDED_FILE_SIZE; use crate::stock::StockMessage; @@ -226,7 +226,7 @@ impl Context { Config::DeleteDeviceAfter => { let ret = self.sql.set_raw_config(self, key, value).await; // Force chatlist reload to delete old messages immediately. - self.emit_event(Event::MsgsChanged { + self.emit_event(EventType::MsgsChanged { msg_id: MsgId::new(0), chat_id: ChatId::new(0), }); diff --git a/src/configure/mod.rs b/src/configure/mod.rs index fbe314b4f..1f0120c99 100644 --- a/src/configure/mod.rs +++ b/src/configure/mod.rs @@ -28,7 +28,7 @@ macro_rules! progress { $progress <= 1000, "value in range 0..1000 expected with: 0=error, 1..999=progress, 1000=success" ); - $context.emit_event($crate::events::Event::ConfigureProgress($progress)); + $context.emit_event($crate::events::EventType::ConfigureProgress($progress)); }; } diff --git a/src/contact.rs b/src/contact.rs index 1fe9451fc..42acdc371 100644 --- a/src/contact.rs +++ b/src/contact.rs @@ -13,7 +13,7 @@ use crate::constants::*; use crate::context::Context; use crate::dc_tools::*; use crate::error::{bail, ensure, format_err, Result}; -use crate::events::Event; +use crate::events::EventType; use crate::key::{DcKey, SignedPublicKey}; use crate::login_param::LoginParam; use crate::message::{MessageState, MsgId}; @@ -245,7 +245,7 @@ impl Contact { let (contact_id, sth_modified) = Contact::add_or_lookup(context, name, addr, Origin::ManuallyCreated).await?; let blocked = Contact::is_blocked_load(context, contact_id).await; - context.emit_event(Event::ContactsChanged( + context.emit_event(EventType::ContactsChanged( if sth_modified == Modifier::Created { Some(contact_id) } else { @@ -273,7 +273,7 @@ impl Contact { .await .is_ok() { - context.emit_event(Event::MsgsChanged { + context.emit_event(EventType::MsgsChanged { chat_id: ChatId::new(0), msg_id: MsgId::new(0), }); @@ -533,7 +533,7 @@ impl Contact { } } if modify_cnt > 0 { - context.emit_event(Event::ContactsChanged(None)); + context.emit_event(EventType::ContactsChanged(None)); } Ok(modify_cnt) @@ -784,7 +784,7 @@ impl Contact { .await { Ok(_) => { - context.emit_event(Event::ContactsChanged(None)); + context.emit_event(EventType::ContactsChanged(None)); return Ok(()); } Err(err) => { @@ -1093,7 +1093,7 @@ async fn set_block_contact(context: &Context, contact_id: u32, new_blocking: boo paramsv![new_blocking, 100, contact_id as i32], ).await.is_ok() { Contact::mark_noticed(context, contact_id).await; - context.emit_event(Event::ContactsChanged(None)); + context.emit_event(EventType::ContactsChanged(None)); } } } @@ -1143,7 +1143,7 @@ pub(crate) async fn set_profile_image( }; if changed { contact.update_param(context).await?; - context.emit_event(Event::ContactsChanged(Some(contact_id))); + context.emit_event(EventType::ContactsChanged(Some(contact_id))); } Ok(()) } diff --git a/src/context.rs b/src/context.rs index 9e652a46f..deba9d786 100644 --- a/src/context.rs +++ b/src/context.rs @@ -14,7 +14,7 @@ use crate::constants::*; use crate::contact::*; use crate::dc_tools::duration_to_str; use crate::error::*; -use crate::events::{Event, EventEmitter, Events}; +use crate::events::{Event, EventEmitter, EventType, Events}; use crate::key::{DcKey, SignedPublicKey}; use crate::login_param::LoginParam; use crate::lot::Lot; @@ -59,6 +59,9 @@ pub struct InnerContext { pub(crate) scheduler: RwLock, pub(crate) ephemeral_task: RwLock>>, + /// Id for this context on the current device. + pub(crate) id: u32, + creation_time: SystemTime, } @@ -86,7 +89,7 @@ pub fn get_info() -> BTreeMap<&'static str, String> { impl Context { /// Creates new context. - pub async fn new(os_name: String, dbfile: PathBuf) -> Result { + pub async fn new(os_name: String, dbfile: PathBuf, id: u32) -> Result { // pretty_env_logger::try_init_timed().ok(); let mut blob_fname = OsString::new(); @@ -96,13 +99,14 @@ impl Context { if !blobdir.exists().await { async_std::fs::create_dir_all(&blobdir).await?; } - Context::with_blobdir(os_name, dbfile, blobdir).await + Context::with_blobdir(os_name, dbfile, blobdir, id).await } - pub async fn with_blobdir( + pub(crate) async fn with_blobdir( os_name: String, dbfile: PathBuf, blobdir: PathBuf, + id: u32, ) -> Result { ensure!( blobdir.is_dir().await, @@ -111,6 +115,7 @@ impl Context { ); let inner = InnerContext { + id, blobdir, dbfile, os_name: Some(os_name), @@ -188,8 +193,11 @@ impl Context { } /// Emits a single event. - pub fn emit_event(&self, event: Event) { - self.events.emit(event); + pub fn emit_event(&self, event: EventType) { + self.events.emit(Event { + id: self.id, + typ: event, + }); } /// Get the next queued event. @@ -197,6 +205,11 @@ impl Context { self.events.get_emitter() } + /// Get the ID of this context. + pub fn get_id(&self) -> u32 { + self.id + } + // Ongoing process allocation/free/check pub async fn alloc_ongoing(&self) -> Result> { @@ -458,6 +471,13 @@ impl Context { self.get_config(Config::ConfiguredMvboxFolder).await == Some(folder_name.as_ref().to_string()) } + + pub fn derive_blobdir(dbfile: &PathBuf) -> PathBuf { + let mut blob_fname = OsString::new(); + blob_fname.push(dbfile.file_name().unwrap_or_default()); + blob_fname.push("-blobs"); + dbfile.with_file_name(blob_fname) + } } impl InnerContext { @@ -510,7 +530,7 @@ mod tests { let tmp = tempfile::tempdir().unwrap(); let dbfile = tmp.path().join("db.sqlite"); std::fs::write(&dbfile, b"123").unwrap(); - let res = Context::new("FakeOs".into(), dbfile.into()).await; + let res = Context::new("FakeOs".into(), dbfile.into(), 1).await; assert!(res.is_err()); } @@ -525,7 +545,9 @@ mod tests { async fn test_blobdir_exists() { let tmp = tempfile::tempdir().unwrap(); let dbfile = tmp.path().join("db.sqlite"); - Context::new("FakeOS".into(), dbfile.into()).await.unwrap(); + Context::new("FakeOS".into(), dbfile.into(), 1) + .await + .unwrap(); let blobdir = tmp.path().join("db.sqlite-blobs"); assert!(blobdir.is_dir()); } @@ -536,7 +558,7 @@ mod tests { let dbfile = tmp.path().join("db.sqlite"); let blobdir = tmp.path().join("db.sqlite-blobs"); std::fs::write(&blobdir, b"123").unwrap(); - let res = Context::new("FakeOS".into(), dbfile.into()).await; + let res = Context::new("FakeOS".into(), dbfile.into(), 1).await; assert!(res.is_err()); } @@ -546,7 +568,9 @@ mod tests { let subdir = tmp.path().join("subdir"); let dbfile = subdir.join("db.sqlite"); let dbfile2 = dbfile.clone(); - Context::new("FakeOS".into(), dbfile.into()).await.unwrap(); + Context::new("FakeOS".into(), dbfile.into(), 1) + .await + .unwrap(); assert!(subdir.is_dir()); assert!(dbfile2.is_file()); } @@ -556,7 +580,7 @@ mod tests { let tmp = tempfile::tempdir().unwrap(); let dbfile = tmp.path().join("db.sqlite"); let blobdir = PathBuf::new(); - let res = Context::with_blobdir("FakeOS".into(), dbfile.into(), blobdir.into()).await; + let res = Context::with_blobdir("FakeOS".into(), dbfile.into(), blobdir.into(), 1).await; assert!(res.is_err()); } @@ -565,7 +589,7 @@ mod tests { let tmp = tempfile::tempdir().unwrap(); let dbfile = tmp.path().join("db.sqlite"); let blobdir = tmp.path().join("blobs"); - let res = Context::with_blobdir("FakeOS".into(), dbfile.into(), blobdir.into()).await; + let res = Context::with_blobdir("FakeOS".into(), dbfile.into(), blobdir.into(), 1).await; assert!(res.is_err()); } diff --git a/src/dc_receive_imf.rs b/src/dc_receive_imf.rs index 4b01ac1d5..8055d5892 100644 --- a/src/dc_receive_imf.rs +++ b/src/dc_receive_imf.rs @@ -12,7 +12,7 @@ use crate::context::Context; use crate::dc_tools::*; use crate::ephemeral::{stock_ephemeral_timer_changed, Timer as EphemeralTimer}; use crate::error::{bail, ensure, format_err, Result}; -use crate::events::Event; +use crate::events::EventType; use crate::headerdef::HeaderDef; use crate::job::{self, Action}; use crate::message::{self, MessageState, MessengerMessage, MsgId}; @@ -93,8 +93,8 @@ pub async fn dc_receive_imf( if let Some(create_event_to_send) = create_event_to_send { for (chat_id, msg_id) in created_db_entries { let event = match create_event_to_send { - CreateEvent::MsgsChanged => Event::MsgsChanged { msg_id, chat_id }, - CreateEvent::IncomingMsg => Event::IncomingMsg { msg_id, chat_id }, + CreateEvent::MsgsChanged => EventType::MsgsChanged { msg_id, chat_id }, + CreateEvent::IncomingMsg => EventType::IncomingMsg { msg_id, chat_id }, }; context.emit_event(event); } @@ -206,7 +206,7 @@ pub async fn dc_receive_imf( .await { Ok(()) => { - context.emit_event(Event::ChatModified(chat_id)); + context.emit_event(EventType::ChatModified(chat_id)); } Err(err) => { warn!(context, "reveive_imf cannot update profile image: {}", err); @@ -935,7 +935,7 @@ async fn save_locations( } } if send_event { - context.emit_event(Event::LocationChanged(Some(from_id))); + context.emit_event(EventType::LocationChanged(Some(from_id))); } } @@ -1239,7 +1239,7 @@ async fn create_or_lookup_group( .await .is_ok() { - context.emit_event(Event::ChatModified(chat_id)); + context.emit_event(EventType::ChatModified(chat_id)); } } } @@ -1298,7 +1298,7 @@ async fn create_or_lookup_group( } if send_EVENT_CHAT_MODIFIED { - context.emit_event(Event::ChatModified(chat_id)); + context.emit_event(EventType::ChatModified(chat_id)); } Ok((chat_id, chat_id_blocked)) } @@ -1435,7 +1435,7 @@ async fn create_or_lookup_adhoc_group( chat::add_to_chat_contacts_table(context, new_chat_id, member_id).await; } - context.emit_event(Event::ChatModified(new_chat_id)); + context.emit_event(EventType::ChatModified(new_chat_id)); Ok((new_chat_id, create_blocked)) } diff --git a/src/dc_tools.rs b/src/dc_tools.rs index 1d887edde..f937a0ef9 100644 --- a/src/dc_tools.rs +++ b/src/dc_tools.rs @@ -15,7 +15,7 @@ use rand::{thread_rng, Rng}; use crate::context::Context; use crate::error::{bail, Error}; -use crate::events::Event; +use crate::events::EventType; pub(crate) fn dc_exactly_one_bit_set(v: i32) -> bool { 0 != v && 0 == v & (v - 1) @@ -286,7 +286,7 @@ pub(crate) async fn dc_delete_file(context: &Context, path: impl AsRef) -> let dpath = format!("{}", path.as_ref().to_string_lossy()); match fs::remove_file(path_abs).await { Ok(_) => { - context.emit_event(Event::DeletedBlobFile(dpath)); + context.emit_event(EventType::DeletedBlobFile(dpath)); true } Err(err) => { diff --git a/src/ephemeral.rs b/src/ephemeral.rs index 0fb311ae7..1bfac1f30 100644 --- a/src/ephemeral.rs +++ b/src/ephemeral.rs @@ -63,7 +63,7 @@ use crate::constants::{ use crate::context::Context; use crate::dc_tools::time; use crate::error::{ensure, Error}; -use crate::events::Event; +use crate::events::EventType; use crate::message::{Message, MessageState, MsgId}; use crate::mimeparser::SystemMessage; use crate::sql; @@ -177,7 +177,7 @@ impl ChatId { ) .await?; - context.emit_event(Event::ChatEphemeralTimerModified { + context.emit_event(EventType::ChatEphemeralTimerModified { chat_id: self, timer, }); @@ -379,7 +379,7 @@ pub async fn schedule_ephemeral_task(context: &Context) { async_std::task::sleep(duration).await; emit_event!( context1, - Event::MsgsChanged { + EventType::MsgsChanged { chat_id: ChatId::new(0), msg_id: MsgId::new(0) } @@ -390,7 +390,7 @@ pub async fn schedule_ephemeral_task(context: &Context) { // Emit event immediately emit_event!( context, - Event::MsgsChanged { + EventType::MsgsChanged { chat_id: ChatId::new(0), msg_id: MsgId::new(0) } diff --git a/src/events.rs b/src/events.rs index d3353862c..f51929790 100644 --- a/src/events.rs +++ b/src/events.rs @@ -1,5 +1,7 @@ //! # Events specification +use std::ops::Deref; + use async_std::path::PathBuf; use async_std::sync::{channel, Receiver, Sender, TrySendError}; use strum::EnumProperty; @@ -54,14 +56,32 @@ impl EventEmitter { async_std::task::block_on(self.recv()) } - /// Blocking async recv of an event. Return `None` if the `Sender` has been droped. + /// Async recv of an event. Return `None` if the `Sender` has been droped. pub async fn recv(&self) -> Option { // TODO: change once we can use async channels internally. self.0.recv().await.ok() } + + pub fn try_recv(&self) -> Result { + self.0.try_recv() + } } -impl Event { +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Event { + pub id: u32, + pub typ: EventType, +} + +impl Deref for Event { + type Target = EventType; + + fn deref(&self) -> &EventType { + &self.typ + } +} + +impl EventType { /// Returns the corresponding Event id. pub fn as_id(&self) -> i32 { self.get_str("id") @@ -72,7 +92,7 @@ impl Event { } #[derive(Debug, Clone, PartialEq, Eq, EnumProperty)] -pub enum Event { +pub enum EventType { /// The library-user may write an informational string to the log. /// Passed to the callback given to dc_context_new(). /// This event should not be reported to the end-user using a popup or something like that. diff --git a/src/imap/mod.rs b/src/imap/mod.rs index c597c629f..ef4ea8b09 100644 --- a/src/imap/mod.rs +++ b/src/imap/mod.rs @@ -19,7 +19,7 @@ use crate::context::Context; use crate::dc_receive_imf::{ dc_receive_imf, from_field_to_contact_id, is_msgrmsg_rfc724_mid_in_list, }; -use crate::events::Event; +use crate::events::EventType; use crate::headerdef::{HeaderDef, HeaderDefMap}; use crate::job::{self, Action}; use crate::login_param::{CertificateChecks, LoginParam}; @@ -284,7 +284,7 @@ impl Imap { .await }; // IMAP connection failures are reported to users - emit_event!(context, Event::ErrorNetwork(message)); + emit_event!(context, EventType::ErrorNetwork(message)); return Err(Error::ConnectionFailed(err.to_string())); } }; @@ -307,7 +307,7 @@ impl Imap { .await; warn!(context, "{} ({})", message, err); - emit_event!(context, Event::ErrorNetwork(message.clone())); + emit_event!(context, EventType::ErrorNetwork(message.clone())); let lock = context.wrong_pw_warning_mutex.lock().await; if self.login_failed_once @@ -445,7 +445,7 @@ impl Imap { self.connected = true; emit_event!( context, - Event::ImapConnected(format!( + EventType::ImapConnected(format!( "IMAP-LOGIN as {}, capabilities: {}", lp.mail_user, caps_list, )) @@ -888,7 +888,7 @@ impl Imap { Ok(_) => { emit_event!( context, - Event::ImapMessageMoved(format!( + EventType::ImapMessageMoved(format!( "IMAP Message {} moved to {}", display_folder_id, dest_folder )) @@ -932,7 +932,7 @@ impl Imap { warn!(context, "Cannot mark {} as \"Deleted\" after copy.", uid); emit_event!( context, - Event::ImapMessageMoved(format!( + EventType::ImapMessageMoved(format!( "IMAP Message {} copied to {} (delete FAILED)", display_folder_id, dest_folder )) @@ -942,7 +942,7 @@ impl Imap { self.config.selected_folder_needs_expunge = true; emit_event!( context, - Event::ImapMessageMoved(format!( + EventType::ImapMessageMoved(format!( "IMAP Message {} copied to {} (delete successfull)", display_folder_id, dest_folder )) @@ -1142,7 +1142,7 @@ impl Imap { } else { emit_event!( context, - Event::ImapMessageDeleted(format!( + EventType::ImapMessageDeleted(format!( "IMAP Message {} marked as deleted [{}]", display_imap_id, message_id )) @@ -1316,7 +1316,7 @@ impl Imap { self.config.selected_folder_needs_expunge = true; match self.select_folder::(context, None).await { Ok(()) => { - emit_event!(context, Event::ImapFolderEmptied(folder.to_string())); + emit_event!(context, EventType::ImapFolderEmptied(folder.to_string())); } Err(err) => { error!(context, "expunge failed {}: {:?}", folder, err); diff --git a/src/imex.rs b/src/imex.rs index c4fbcfaf2..4b2f7c67e 100644 --- a/src/imex.rs +++ b/src/imex.rs @@ -16,7 +16,7 @@ use crate::context::Context; use crate::dc_tools::*; use crate::e2ee; use crate::error::*; -use crate::events::Event; +use crate::events::EventType; use crate::key::{self, DcKey, DcSecretKey, SignedPublicKey, SignedSecretKey}; use crate::message::{Message, MsgId}; use crate::mimeparser::SystemMessage; @@ -369,7 +369,7 @@ async fn imex_inner( ensure!(param.is_some(), "No Import/export dir/file given."); info!(context, "Import/export process started."); - context.emit_event(Event::ImexProgress(10)); + context.emit_event(EventType::ImexProgress(10)); ensure!(context.sql.is_open().await, "Database not opened."); @@ -393,11 +393,11 @@ async fn imex_inner( match success { Ok(()) => { info!(context, "IMEX successfully completed"); - context.emit_event(Event::ImexProgress(1000)); + context.emit_event(EventType::ImexProgress(1000)); Ok(()) } Err(err) => { - context.emit_event(Event::ImexProgress(0)); + context.emit_event(EventType::ImexProgress(0)); bail!("IMEX FAILED to complete: {}", err); } } @@ -487,7 +487,7 @@ async fn import_backup(context: &Context, backup_to_import: impl AsRef) -> if permille > 990 { permille = 990 } - context.emit_event(Event::ImexProgress(permille)); + context.emit_event(EventType::ImexProgress(permille)); if file_blob.is_empty() { continue; } @@ -563,7 +563,7 @@ async fn export_backup(context: &Context, dir: impl AsRef) -> Result<()> { dest_sql .set_raw_config_int(context, "backup_time", now as i32) .await?; - context.emit_event(Event::ImexFileWritten(dest_path_filename)); + context.emit_event(EventType::ImexFileWritten(dest_path_filename)); Ok(()) } }; @@ -602,7 +602,7 @@ async fn add_files_to_export(context: &Context, sql: &Sql) -> Result<()> { } processed_files_cnt += 1; let permille = max(min(processed_files_cnt * 1000 / total_files_cnt, 990), 10); - context.emit_event(Event::ImexProgress(permille)); + context.emit_event(EventType::ImexProgress(permille)); let name_f = entry.file_name(); let name = name_f.to_string_lossy(); @@ -769,7 +769,7 @@ where if res.is_err() { error!(context, "Cannot write key to {}", file_name.display()); } else { - context.emit_event(Event::ImexFileWritten(file_name)); + context.emit_event(EventType::ImexFileWritten(file_name)); } res } diff --git a/src/job.rs b/src/job.rs index 171cd33d0..26e078977 100644 --- a/src/job.rs +++ b/src/job.rs @@ -23,7 +23,7 @@ use crate::context::Context; use crate::dc_tools::*; use crate::ephemeral::load_imap_deletion_msgid; use crate::error::{bail, ensure, format_err, Error, Result}; -use crate::events::Event; +use crate::events::EventType; use crate::imap::*; use crate::location; use crate::login_param::LoginParam; @@ -737,7 +737,7 @@ async fn set_delivered(context: &Context, msg_id: MsgId) { ) .await .unwrap_or_default(); - context.emit_event(Event::MsgDelivered { chat_id, msg_id }); + context.emit_event(EventType::MsgDelivered { chat_id, msg_id }); } /// Constructs a job for sending a message. diff --git a/src/lib.rs b/src/lib.rs index db5e598fa..fbf82d0bf 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -79,6 +79,8 @@ mod dehtml; pub mod dc_receive_imf; pub mod dc_tools; +pub mod accounts; + /// if set imap/incoming and smtp/outgoing MIME messages will be printed pub const DCC_MIME_DEBUG: &str = "DCC_MIME_DEBUG"; diff --git a/src/location.rs b/src/location.rs index dabd54f5d..c09cbd957 100644 --- a/src/location.rs +++ b/src/location.rs @@ -9,7 +9,7 @@ use crate::constants::*; use crate::context::*; use crate::dc_tools::*; use crate::error::{ensure, Error}; -use crate::events::Event; +use crate::events::EventType; use crate::job::{self, Job}; use crate::message::{Message, MsgId}; use crate::mimeparser::SystemMessage; @@ -227,7 +227,7 @@ pub async fn send_locations_to_chat(context: &Context, chat_id: ChatId, seconds: .await; chat::add_info_msg(context, chat_id, stock_str).await; } - context.emit_event(Event::ChatModified(chat_id)); + context.emit_event(EventType::ChatModified(chat_id)); if 0 != seconds { schedule_maybe_send_locations(context, false).await; job::add( @@ -301,7 +301,7 @@ pub async fn set(context: &Context, latitude: f64, longitude: f64, accuracy: f64 } } if continue_streaming { - context.emit_event(Event::LocationChanged(Some(DC_CONTACT_ID_SELF))); + context.emit_event(EventType::LocationChanged(Some(DC_CONTACT_ID_SELF))); }; schedule_maybe_send_locations(context, false).await; } @@ -381,7 +381,7 @@ pub async fn delete_all(context: &Context) -> Result<(), Error> { .sql .execute("DELETE FROM locations;", paramsv![]) .await?; - context.emit_event(Event::LocationChanged(None)); + context.emit_event(EventType::LocationChanged(None)); Ok(()) } @@ -715,7 +715,7 @@ pub(crate) async fn job_maybe_send_locations_ended( .stock_system_msg(StockMessage::MsgLocationDisabled, "", "", 0) .await; chat::add_info_msg(context, chat_id, stock_str).await; - context.emit_event(Event::ChatModified(chat_id)); + context.emit_event(EventType::ChatModified(chat_id)); } } job::Status::Finished(Ok(())) diff --git a/src/log.rs b/src/log.rs index 9b253b93d..bcc9d1d43 100644 --- a/src/log.rs +++ b/src/log.rs @@ -11,7 +11,7 @@ macro_rules! info { file = file!(), line = line!(), msg = &formatted); - emit_event!($ctx, $crate::Event::Info(full)); + emit_event!($ctx, $crate::EventType::Info(full)); }}; } @@ -26,7 +26,7 @@ macro_rules! warn { file = file!(), line = line!(), msg = &formatted); - emit_event!($ctx, $crate::Event::Warning(full)); + emit_event!($ctx, $crate::EventType::Warning(full)); }}; } @@ -37,7 +37,7 @@ macro_rules! error { }; ($ctx:expr, $msg:expr, $($args:expr),* $(,)?) => {{ let formatted = format!($msg, $($args),*); - emit_event!($ctx, $crate::Event::Error(formatted)); + emit_event!($ctx, $crate::EventType::Error(formatted)); }}; } @@ -48,7 +48,7 @@ macro_rules! error_network { }; ($ctx:expr, $msg:expr, $($args:expr),* $(,)?) => {{ let formatted = format!($msg, $($args),*); - emit_event!($ctx, $crate::Event::ErrorNetwork(formatted)); + emit_event!($ctx, $crate::EventType::ErrorNetwork(formatted)); }}; } diff --git a/src/message.rs b/src/message.rs index bb1e4607b..092569785 100644 --- a/src/message.rs +++ b/src/message.rs @@ -12,7 +12,7 @@ use crate::contact::*; use crate::context::*; use crate::dc_tools::*; use crate::error::{ensure, Error}; -use crate::events::Event; +use crate::events::EventType; use crate::job::{self, Action}; use crate::lot::{Lot, LotState, Meaning}; use crate::mimeparser::{FailureReport, SystemMessage}; @@ -1118,7 +1118,7 @@ pub async fn delete_msgs(context: &Context, msg_ids: &[MsgId]) { } if !msg_ids.is_empty() { - context.emit_event(Event::MsgsChanged { + context.emit_event(EventType::MsgsChanged { chat_id: ChatId::new(0), msg_id: MsgId::new(0), }); @@ -1209,7 +1209,7 @@ pub async fn markseen_msgs(context: &Context, msg_ids: Vec) -> bool { } if send_event { - context.emit_event(Event::MsgsChanged { + context.emit_event(EventType::MsgsChanged { chat_id: ChatId::new(0), msg_id: MsgId::new(0), }); @@ -1376,7 +1376,7 @@ pub async fn set_msg_failed(context: &Context, msg_id: MsgId, error: Option context.emit_event(Event::MsgFailed { + Ok(_) => context.emit_event(EventType::MsgFailed { chat_id: msg.chat_id, msg_id, }), @@ -1551,7 +1551,7 @@ pub(crate) async fn handle_ndn( .await, ) .await; - context.emit_event(Event::ChatModified(chat_id)); + context.emit_event(EventType::ChatModified(chat_id)); } } } diff --git a/src/mimeparser.rs b/src/mimeparser.rs index 7016e743c..c51be5bb7 100644 --- a/src/mimeparser.rs +++ b/src/mimeparser.rs @@ -16,7 +16,7 @@ use crate::dc_tools::*; use crate::dehtml::dehtml; use crate::e2ee; use crate::error::{bail, Result}; -use crate::events::Event; +use crate::events::EventType; use crate::headerdef::{HeaderDef, HeaderDefMap}; use crate::key::Fingerprint; use crate::location; @@ -1001,7 +1001,7 @@ impl MimeMessage { if let Some((chat_id, msg_id)) = message::handle_mdn(context, from_id, original_message_id, sent_timestamp).await { - context.emit_event(Event::MsgRead { chat_id, msg_id }); + context.emit_event(EventType::MsgRead { chat_id, msg_id }); } } } diff --git a/src/securejoin.rs b/src/securejoin.rs index 200791984..5df5f65cb 100644 --- a/src/securejoin.rs +++ b/src/securejoin.rs @@ -12,7 +12,7 @@ use crate::contact::*; use crate::context::Context; use crate::e2ee::*; use crate::error::{bail, Error}; -use crate::events::Event; +use crate::events::EventType; use crate::headerdef::HeaderDef; use crate::key::{DcKey, Fingerprint, SignedPublicKey}; use crate::lot::LotState; @@ -32,7 +32,7 @@ macro_rules! joiner_progress { $progress >= 0 && $progress <= 1000, "value in range 0..1000 expected with: 0=error, 1..999=progress, 1000=success" ); - $context.emit_event($crate::events::Event::SecurejoinJoinerProgress { + $context.emit_event($crate::events::EventType::SecurejoinJoinerProgress { contact_id: $contact_id, progress: $progress, }); @@ -45,7 +45,7 @@ macro_rules! inviter_progress { $progress >= 0 && $progress <= 1000, "value in range 0..1000 expected with: 0=error, 1..999=progress, 1000=success" ); - $context.emit_event($crate::events::Event::SecurejoinInviterProgress { + $context.emit_event($crate::events::EventType::SecurejoinInviterProgress { contact_id: $contact_id, progress: $progress, }); @@ -653,7 +653,7 @@ pub(crate) async fn handle_securejoin_handshake( Contact::scaleup_origin_by_id(context, contact_id, Origin::SecurejoinInvited).await; info!(context, "Auth verified.",); secure_connection_established(context, contact_chat_id).await; - emit_event!(context, Event::ContactsChanged(Some(contact_id))); + emit_event!(context, EventType::ContactsChanged(Some(contact_id))); inviter_progress!(context, contact_id, 600); if join_vg { // the vg-member-added message is special: @@ -773,7 +773,7 @@ pub(crate) async fn handle_securejoin_handshake( return Ok(abort_retval); } Contact::scaleup_origin_by_id(context, contact_id, Origin::SecurejoinJoined).await; - emit_event!(context, Event::ContactsChanged(None)); + emit_event!(context, EventType::ContactsChanged(None)); let cg_member_added = mime_message .get(HeaderDef::ChatGroupMemberAdded) .map(|s| s.as_str()) @@ -960,7 +960,7 @@ async fn secure_connection_established(context: &Context, contact_chat_id: ChatI .stock_string_repl_str(StockMessage::ContactVerified, addr) .await; chat::add_info_msg(context, contact_chat_id, msg).await; - emit_event!(context, Event::ChatModified(contact_chat_id)); + emit_event!(context, EventType::ChatModified(contact_chat_id)); } async fn could_not_establish_secure_connection( @@ -1073,7 +1073,7 @@ pub async fn handle_degrade_event( .await; chat::add_info_msg(context, contact_chat_id, msg).await; - emit_event!(context, Event::ChatModified(contact_chat_id)); + emit_event!(context, EventType::ChatModified(contact_chat_id)); } } Ok(()) diff --git a/src/smtp/mod.rs b/src/smtp/mod.rs index 2e4ad6e57..05707ecac 100644 --- a/src/smtp/mod.rs +++ b/src/smtp/mod.rs @@ -9,7 +9,7 @@ use async_smtp::*; use crate::constants::*; use crate::context::Context; -use crate::events::Event; +use crate::events::EventType; use crate::login_param::{dc_build_tls, CertificateChecks, LoginParam}; use crate::oauth2::*; use crate::provider::get_provider_info; @@ -102,7 +102,7 @@ impl Smtp { } if lp.send_server.is_empty() || lp.send_port == 0 { - context.emit_event(Event::ErrorNetwork("SMTP bad parameters.".into())); + context.emit_event(EventType::ErrorNetwork("SMTP bad parameters.".into())); return Err(Error::BadParameters); } @@ -187,14 +187,14 @@ impl Smtp { ) .await; - emit_event!(context, Event::ErrorNetwork(message)); + emit_event!(context, EventType::ErrorNetwork(message)); return Err(Error::ConnectionFailure(err)); } self.transport = Some(trans); self.last_success = Some(SystemTime::now()); - context.emit_event(Event::SmtpConnected(format!( + context.emit_event(EventType::SmtpConnected(format!( "SMTP-LOGIN as {} ok", lp.send_user, ))); diff --git a/src/smtp/send.rs b/src/smtp/send.rs index 821636549..c4b06745a 100644 --- a/src/smtp/send.rs +++ b/src/smtp/send.rs @@ -4,7 +4,7 @@ use super::Smtp; use async_smtp::*; use crate::context::Context; -use crate::events::Event; +use crate::events::EventType; use std::time::Duration; pub type Result = std::result::Result; @@ -55,7 +55,7 @@ impl Smtp { .await .map_err(Error::SendError)?; - context.emit_event(Event::SmtpMessageSent(format!( + context.emit_event(EventType::SmtpMessageSent(format!( "Message len={} was smtp-sent to {}", message_len_bytes, recipients_display ))); diff --git a/src/test_utils.rs b/src/test_utils.rs index ec3f6069d..cccd7548a 100644 --- a/src/test_utils.rs +++ b/src/test_utils.rs @@ -27,9 +27,14 @@ impl TestContext { /// /// [Context]: crate::context::Context pub async fn new() -> Self { + use rand::Rng; + let dir = tempdir().unwrap(); let dbfile = dir.path().join("db.sqlite"); - let ctx = Context::new("FakeOS".into(), dbfile.into()).await.unwrap(); + let id = rand::thread_rng().gen(); + let ctx = Context::new("FakeOS".into(), dbfile.into(), id) + .await + .unwrap(); Self { ctx, dir } } diff --git a/tests/stress.rs b/tests/stress.rs index ee00e713f..09ab1924e 100644 --- a/tests/stress.rs +++ b/tests/stress.rs @@ -99,9 +99,14 @@ struct TestContext { } async fn create_test_context() -> TestContext { + use rand::Rng; + let dir = tempdir().unwrap(); let dbfile = dir.path().join("db.sqlite"); - let ctx = Context::new("FakeOs".into(), dbfile.into()).await.unwrap(); + let id = rand::thread_rng().gen(); + let ctx = Context::new("FakeOs".into(), dbfile.into(), id) + .await + .unwrap(); TestContext { ctx, dir } }