Merge pull request #1784 from deltachat/feat/multiii

This commit is contained in:
Friedel Ziegelmayer
2020-08-11 12:20:32 +02:00
committed by GitHub
32 changed files with 1513 additions and 326 deletions

81
Cargo.lock generated
View File

@@ -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]]

View File

@@ -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]

View File

@@ -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"]

View File

@@ -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.

View File

@@ -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)
}

View File

@@ -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 <id> 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 {}.",

View File

@@ -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<String>) -> 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);
}
});

View File

@@ -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);
}
});

533
src/accounts.rs Normal file
View File

@@ -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<RwLock<HashMap<u32, Context>>>,
}
impl Accounts {
/// Loads or creates an accounts folder at the given `dir`.
pub async fn new(os_name: String, dir: PathBuf) -> Result<Self> {
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<Self> {
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<Context> {
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<u32> {
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<u32> {
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<u32> {
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<u32> {
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<Event> {
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<Event> {
futures::future::poll_fn(|cx| Pin::new(self).recv_poll(cx)).await
}
fn recv_poll(self: Pin<&Self>, _cx: &mut TaskContext<'_>) -> Poll<Option<Event>> {
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<EmitterWrapper>);
#[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<RwLock<InnerConfig>>,
}
#[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<AccountConfig>,
}
impl Config {
pub async fn new(os_name: String, dir: &PathBuf) -> Result<Self> {
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<Self> {
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<HashMap<u32, Context>> {
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<AccountConfig> {
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<AccountConfig> {
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()
);
}
}

View File

@@ -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)
}

View File

@@ -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<ChatId
chat.id.unblock(context).await;
// Sending with 0s as data since multiple messages may have changed.
context.emit_event(Event::MsgsChanged {
context.emit_event(EventType::MsgsChanged {
chat_id: ChatId::new(0),
msg_id: MsgId::new(0),
});
@@ -1188,7 +1188,7 @@ pub async fn create_by_contact_id(context: &Context, contact_id: u32) -> 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),
});

View File

@@ -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),
});

View File

@@ -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));
};
}

View File

@@ -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(())
}

View File

@@ -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<Scheduler>,
pub(crate) ephemeral_task: RwLock<Option<task::JoinHandle<()>>>,
/// 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<Context> {
pub async fn new(os_name: String, dbfile: PathBuf, id: u32) -> Result<Context> {
// 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<Context> {
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<Receiver<()>> {
@@ -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());
}

View File

@@ -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))
}

View File

@@ -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<Path>) ->
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) => {

View File

@@ -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)
}

View File

@@ -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<Event> {
// TODO: change once we can use async channels internally.
self.0.recv().await.ok()
}
pub fn try_recv(&self) -> Result<Event, async_std::sync::TryRecvError> {
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.

View File

@@ -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::<String>(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);

View File

@@ -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<Path>) ->
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<Path>) -> 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
}

View File

@@ -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.

View File

@@ -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";

View File

@@ -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(()))

View File

@@ -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));
}};
}

View File

@@ -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<MsgId>) -> 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<impl
)
.await
{
Ok(_) => 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));
}
}
}

View File

@@ -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 });
}
}
}

View File

@@ -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(())

View File

@@ -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,
)));

View File

@@ -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<T> = std::result::Result<T, Error>;
@@ -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
)));

View File

@@ -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 }
}

View File

@@ -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 }
}