Compare commits

..

2 Commits

Author SHA1 Message Date
B. Petersen
ea9252925c re-enabling smtp_config_task - still fine 2020-10-22 00:03:28 +02:00
B. Petersen
7e3029aa9c this works with https://github.com/deltachat/deltachat-android/issues/1676 2020-10-21 23:38:33 +02:00
39 changed files with 442 additions and 1199 deletions

View File

@@ -1,63 +1,5 @@
# Changelog
## 1.50.0
- do not fetch emails in between inbox_watch disabled and enabled again #2087
- fix: do not fetch from INBOX if inbox_watch is disabled #2085
- fix: do not use STARTTLS when PLAIN connection is requested
and do not allow downgrade if STARTTLS is not available #2071
## 1.49.0
- add timestamps to image and video filenames #2068
- forbid quoting messages from another context #2069
- fix: preserve quotes in messages with attachments #2070
## 1.48.0
- `fetch_existing` renamed to `fetch_existing_msgs` and disabled by default
#2035 #2042
- skip fetch existing messages/contacts if config-option `bot` set #2017
- always log why a message is sorted to trash #2045
- display a quote if top posting is detected #2047
- add ephemeral task cancellation to `dc_stop_io()`;
before, there was no way to quickly terminate pending ephemeral tasks #2051
- when saved-messages chat is deleted,
a device-message about recreation is added #2050
- use `max_smtp_rcpt_to` from provider-db,
sending messages to many recipients in configurable chunks #2056
- fix handling of empty autoconfigure files #2027
- fix adding saved messages to wrong chats on multi-device #2034 #2039
- fix hang on android4.4 and other systems
by adding a workaround to executer-blocking-handling bug #2040
- fix secret key export/import roundtrip #2048
- fix mistakenly unarchived chats #2057
- fix outdated-reminder test that fails only 7 days a year,
including halloween :) #2059
- improve python bindings #2021 #2036 #2038
- update provider-database #2037
## 1.47.0
- breaking change: `dc_update_device_chats()` removed;
@@ -85,7 +27,6 @@
- configure now collects recent contacts and fetches last messages
unless disabled by `fetch_existing` config-option #1913 #2003
EDIT: `fetch_existing` renamed to `fetch_existing_msgs` in 1.48.0 #2042
- emit `DC_EVENT_CHAT_MODIFIED` on contact rename
and set contact-id on `DC_EVENT_CONTACTS_CHANGED` #1935 #1936 #1937
@@ -99,8 +40,6 @@
- mark all failed messages as failed when receiving an NDN #1993
- check some easy cases for bad system clock and outdated app #1901
- fix import temporary directory usage #1929
- fix forcing encryption for reset peers #1998

216
Cargo.lock generated
View File

@@ -452,27 +452,6 @@ version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd"
[[package]]
name = "base64-stream"
version = "1.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6596cd4b981cb9e85a9bddf2d1c3a76ebffe64f9e6b0ea6f053d810aea7807c"
dependencies = [
"base64 0.13.0",
"educe",
"generic-array",
]
[[package]]
name = "bincode"
version = "1.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f30d3a39baa26f9651f17b375061f3233dde33424a8b72b0dbe93a68a0bc896d"
dependencies = [
"byteorder",
"serde",
]
[[package]]
name = "bit-set"
version = "0.5.2"
@@ -706,16 +685,6 @@ dependencies = [
"winapi",
]
[[package]]
name = "chrono-tz"
version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2554a3155fec064362507487171dcc4edc3df60cb10f3a1fb10ed8094822b120"
dependencies = [
"chrono",
"parse-zoneinfo",
]
[[package]]
name = "circular"
version = "0.3.0"
@@ -843,7 +812,7 @@ dependencies = [
"clap",
"criterion-plot",
"csv",
"itertools 0.9.0",
"itertools",
"lazy_static",
"num-traits",
"oorandom",
@@ -865,7 +834,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e022feadec601fba1649cfa83586381a4ad31c6bf3a9ab7d408118b05dd9889d"
dependencies = [
"cast",
"itertools 0.9.0",
"itertools",
]
[[package]]
@@ -1022,12 +991,6 @@ version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d4d0e2d24e5ee3b23a01de38eefdcd978907890701f08ffffd4cb457ca4ee8d6"
[[package]]
name = "debug-helper"
version = "0.3.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a8a5bb894f24f42c247f19b25928a88e31867c0f84552c05df41a9dd527435e"
[[package]]
name = "deflate"
version = "0.8.6"
@@ -1040,7 +1003,7 @@ dependencies = [
[[package]]
name = "deltachat"
version = "1.50.0"
version = "1.47.0"
dependencies = [
"ansi_term 0.12.1",
"anyhow",
@@ -1066,10 +1029,9 @@ dependencies = [
"futures",
"futures-lite",
"hex",
"ical",
"image",
"indexmap",
"itertools 0.9.0",
"itertools",
"kamadak-exif",
"lettre_email",
"libc",
@@ -1084,7 +1046,7 @@ dependencies = [
"pretty_assertions",
"pretty_env_logger",
"proptest",
"quick-xml 0.18.1",
"quick-xml",
"r2d2",
"r2d2_sqlite",
"rand",
@@ -1105,7 +1067,6 @@ dependencies = [
"toml",
"url",
"uuid",
"vcard",
]
[[package]]
@@ -1118,7 +1079,7 @@ dependencies = [
[[package]]
name = "deltachat_ffi"
version = "1.50.0"
version = "1.47.0"
dependencies = [
"anyhow",
"async-std",
@@ -1248,18 +1209,6 @@ dependencies = [
"zeroize",
]
[[package]]
name = "educe"
version = "0.4.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7260c7e6e656fc7702a1aa8d5b498a1a69aa84ac4ffcd5501b7d26939f368a93"
dependencies = [
"enum-ordinalize",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "either"
version = "1.6.1"
@@ -1386,19 +1335,6 @@ dependencies = [
"syn",
]
[[package]]
name = "enum-ordinalize"
version = "3.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1676e1daadfd216bda88d3a6fedd1bf53b829a085f5cc4d81c6f3054f50ef983"
dependencies = [
"num-bigint 0.3.1",
"num-traits",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "env_logger"
version = "0.7.1"
@@ -1427,28 +1363,6 @@ version = "2.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f7531096570974c3a9dcf9e4b8e1cede1ec26cf5046219fb3b9d897503b9be59"
[[package]]
name = "failure"
version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d32e9bd16cc02eae7db7ef620b392808b89f6a5e16bb3497d159c6b92a0f4f86"
dependencies = [
"backtrace",
"failure_derive",
]
[[package]]
name = "failure_derive"
version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aa4da3c766cd7a0db8242e326e9e4e081edd567072893ed320008189715366a4"
dependencies = [
"proc-macro2",
"quote",
"syn",
"synstructure",
]
[[package]]
name = "fallible-iterator"
version = "0.2.0"
@@ -1840,15 +1754,6 @@ dependencies = [
"quick-error",
]
[[package]]
name = "ical"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4a9f7215ad0d77e69644570dee000c7678a47ba7441062c1b5f918adde0d73cf"
dependencies = [
"thiserror",
]
[[package]]
name = "ident_case"
version = "1.0.1"
@@ -1928,15 +1833,6 @@ dependencies = [
"winreg",
]
[[package]]
name = "itertools"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f56a2d0bc861f9165be4eb3442afd3c236d8a98afd426f65d92324ae1091a484"
dependencies = [
"either",
]
[[package]]
name = "itertools"
version = "0.9.0"
@@ -2265,17 +2161,6 @@ dependencies = [
"num-traits",
]
[[package]]
name = "num-bigint"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e9a41747ae4633fce5adffb4d2e81ffc5e89593cb19917f8fb2cc5ff76507bf"
dependencies = [
"autocfg 1.0.1",
"num-integer",
"num-traits",
]
[[package]]
name = "num-bigint-dig"
version = "0.6.0"
@@ -2369,12 +2254,6 @@ version = "1.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "260e51e7efe62b592207e9e13a68e43692a7a279171d6ba57abd208bf23645ad"
[[package]]
name = "oncemutex"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44d11de466f4a3006fe8a5e7ec84e93b79c70cb992ae0aa0eb631ad2df8abfe2"
[[package]]
name = "oorandom"
version = "11.1.2"
@@ -2495,15 +2374,6 @@ dependencies = [
"winapi",
]
[[package]]
name = "parse-zoneinfo"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c705f256449c60da65e11ff6626e0c16a0a0b96aaa348de61376b249bc340f41"
dependencies = [
"regex",
]
[[package]]
name = "pem"
version = "0.8.1"
@@ -2570,26 +2440,6 @@ dependencies = [
"zeroize",
]
[[package]]
name = "phonenumber"
version = "0.2.4+8.11.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "207b3a8b4b30da166f6d8175ad39b86aa84d190965be4533af3d5541754de358"
dependencies = [
"bincode",
"either",
"failure",
"fnv",
"itertools 0.8.2",
"lazy_static",
"nom 5.1.2",
"quick-xml 0.17.2",
"regex",
"regex-cache",
"serde",
"serde_derive",
]
[[package]]
name = "pin-project"
version = "0.4.25"
@@ -2750,15 +2600,6 @@ version = "1.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0"
[[package]]
name = "quick-xml"
version = "0.17.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fe1e430bdcf30c9fdc25053b9c459bb1a4672af4617b6c783d7d91dc17c6bbb0"
dependencies = [
"memchr",
]
[[package]]
name = "quick-xml"
version = "0.18.1"
@@ -2918,18 +2759,6 @@ dependencies = [
"byteorder",
]
[[package]]
name = "regex-cache"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32c86351f6af6bbf23b4c5f73ee4fdfe92d298fdf28572ea4f69494cabe38699"
dependencies = [
"lru-cache",
"oncemutex",
"regex",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.6.18"
@@ -3308,7 +3137,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "692ca13de57ce0613a363c8c2f1de925adebc81b04c923ac60c5488bb44abe4b"
dependencies = [
"chrono",
"num-bigint 0.2.6",
"num-bigint",
"num-traits",
]
@@ -3788,37 +3617,6 @@ dependencies = [
"serde",
]
[[package]]
name = "validators"
version = "0.20.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9af4acf80e223db35334bfb3b09421c54ed259ca203b5189177e61e3b9e1c2a3"
dependencies = [
"debug-helper",
"lazy_static",
"num-traits",
"phonenumber",
"regex",
]
[[package]]
name = "vcard"
version = "0.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c988b5461430fd8d078237c8a9436c8e2f810f944cba3086e3ce99cd54652caf"
dependencies = [
"base64-stream",
"chrono",
"chrono-tz",
"idna",
"lazy_static",
"mime",
"mime_guess",
"percent-encoding",
"regex",
"validators",
]
[[package]]
name = "vcpkg"
version = "0.2.10"

View File

@@ -1,6 +1,6 @@
[package]
name = "deltachat"
version = "1.50.0"
version = "1.47.0"
authors = ["Delta Chat Developers (ML) <delta@codespeak.net>"]
edition = "2018"
license = "MPL-2.0"
@@ -61,8 +61,6 @@ url = "2.1.1"
async-std-resolver = "0.19.5"
async-tar = "0.3.0"
uuid = { version = "0.8", features = ["serde", "v4"] }
vcard = "0.4.6"
ical = { version = "0.7.0", default-features = false, features = ["vcard"] }
pretty_env_logger = { version = "0.4.0", optional = true }
log = {version = "0.4.8", optional = true }

View File

@@ -1,6 +1,6 @@
[package]
name = "deltachat_ffi"
version = "1.50.0"
version = "1.47.0"
description = "Deltachat FFI"
authors = ["Delta Chat Developers (ML) <delta@codespeak.net>"]
edition = "2018"

View File

@@ -36,18 +36,16 @@ typedef struct _dc_accounts_event_emitter dc_accounts_event_emitter_t;
*
* First of all, you have to **create a context object**
* bound to a database.
* The database is a normal SQLite file with a "blob directory" beside it.
* This will create "example.db" database and "example.db-blobs"
* directory if they don't exist already:
* The database is a normal sqlite-file and is created as needed:
*
* ~~~
* dc_context_t* context = dc_context_new(NULL, "example.db", NULL);
* ~~~
*
* After that, make sure you can **receive events from the context**.
* After that, make sure, you can **receive events from the context**.
* For that purpose, create an event emitter you can ask for events.
* If there is no event, the emitter will wait until there is one,
* so, in many situations, you will do this in a thread:
* If there are no event, the emitter will wait until there is one,
* so, in many situations you will do this in a thread:
*
* ~~~
* void* event_handler(void* context)
@@ -68,7 +66,7 @@ typedef struct _dc_accounts_event_emitter dc_accounts_event_emitter_t;
*
* The example above uses "pthreads",
* however, you can also use anything else for thread handling.
* All deltachat-core functions, unless stated otherwise, are thread-safe.
* All deltachat-core-functions, unless stated otherwise, are thread-safe.
*
* Now you can **configure the context:**
*
@@ -79,13 +77,13 @@ typedef struct _dc_accounts_event_emitter dc_accounts_event_emitter_t;
* dc_configure(context);
* ~~~
*
* dc_configure() returns immediately.
* The configuration itself runs in the background and may take a while.
* dc_configure() returns immediately,
* the configuration itself runs in background and may take a while.
* Once done, the #DC_EVENT_CONFIGURE_PROGRESS reports success
* to the event_handler() you've defined above.
*
* The configuration result is saved in the database.
* On subsequent starts it is not needed to call dc_configure()
* The configuration result is saved in the database,
* on subsequent starts it is not needed to call dc_configure()
* (you can check this using dc_is_configured()).
*
* On a successfully configured context,
@@ -107,12 +105,12 @@ typedef struct _dc_accounts_event_emitter dc_accounts_event_emitter_t;
*
* dc_send_text_msg() returns immediately;
* the sending itself is done in the background.
* If you check the testing address (bob),
* you should receive a normal email.
* Answer this email in any email program with "Got it!",
* If you check the testing address (bob)
* and you should have received a normal email.
* Answer this email in any email program with "Got it!"
* and the IO you started above will **receive the message**.
*
* You can then **list all messages** of a chat as follows:
* You can then **list all messages** of a chat as follow:
*
* ~~~
* dc_array_t* msglist = dc_get_chat_msgs(context, chat_id, 0, 0);
@@ -183,12 +181,18 @@ typedef struct _dc_accounts_event_emitter dc_accounts_event_emitter_t;
* opened, connected and mails are fetched.
*
* @memberof dc_context_t
* @param os_name is only for decorative use.
* @param os_name is only for decorative use
* and is shown eg. in the `X-Mailer:` header
* in the form "Delta Chat Core <version>/<os_name>".
* You can give the name of the app, the operating system,
* the used environment and/or the version here.
* It is okay to give NULL, in this case `X-Mailer:` header
* is set to "Delta Chat Core <version>".
* @param dbfile The file to use to store the database,
* something like `~/file` won't work, use absolute paths.
* @param blobdir Deprecated, pass NULL or an empty string here.
* @param blobdir A directory to store the blobs in; a trailing slash is not needed.
* If you pass NULL or the empty string, deltachat-core creates a directory
* beside _dbfile_ with the same name and the suffix `-blobs`.
* @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.
@@ -201,9 +205,14 @@ dc_context_t* dc_context_new (const char* os_name, const char* d
/**
* Free a context object.
* If app runs can only be terminated by a forced kill, this may be superfluous.
* Before the context object is freed, connections to SMTP, IMAP and database
* 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().
* 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(),
@@ -221,7 +230,7 @@ void dc_context_unref (dc_context_t* context);
* 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 e.g. by dc_accounts_get_account() or dc_context_new().
* @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);
@@ -229,7 +238,7 @@ 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, such as "new message", "message read" etc.
* 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_get_next_event() on the emitter.
*
@@ -240,7 +249,7 @@ uint32_t dc_get_id (dc_context_t* context);
*
* Note: Use only one event emitter per context.
* Having more than one event emitter running at the same time on the same context
* will result in events being randomly delivered to one of the emitters.
* will result in events randomly delivered to the one or to the other.
*/
dc_event_emitter_t* dc_get_event_emitter(dc_context_t* context);
@@ -341,7 +350,7 @@ char* dc_get_blobdir (const dc_context_t* context);
* The type `jitsi:` may be handled by external apps.
* If no type is prefixed, the videochat is handled completely in a browser.
* - `bot` = Set to "1" if this is a bot. E.g. prevents adding the "Device messages" and "Saved messages" chats.
* - `fetch_existing_msgs` = 1=fetch most recent existing messages on configure (default),
* - `fetch_existing` = 1=fetch most recent existing messages on configure (default),
* 0=do not fetch existing messages on configure.
* In both cases, existing recipients are added to the contact database.
*
@@ -465,7 +474,7 @@ char* dc_get_oauth2_url (dc_context_t* context, const char*
/**
* Configure a context.
* During configuration IO must not be started, if needed stop IO using dc_stop_io() first.
* While configuration IO must not be started, if needed stop IO using dc_stop_io() first.
* If the context is already configured,
* this function will try to change the configuration.
*
@@ -553,12 +562,9 @@ void dc_start_io (dc_context_t* context);
int dc_is_io_running(const dc_context_t* context);
/**
* Stop job, IMAP, SMTP and other tasks and return when they
* are finished.
* Stop job and IMAP/SMTP tasks and return when they are finished.
* If IO is not running, nothing happens.
* To check the current IO state, use dc_is_io_running().
* Even if IO is not running, there may be pending tasks,
* so this function should always be called before releasing
* context to ensure clean termination of event loop.
*
* If the context was created by the dc_accounts_t account manager,
* use dc_accounts_stop_io() instead of this function.
@@ -571,7 +577,7 @@ void dc_stop_io(dc_context_t* context);
/**
* This function should be called when there is a hint
* that the network is available again,
* e.g. as a response to system event reporting network availability.
* eg. as a response to system event reporting network availability.
* The library will try to send pending messages out immediately.
*
* Moreover, to have a reliable state
@@ -632,7 +638,7 @@ int dc_preconfigure_keypair (dc_context_t* context, const cha
* Clients should not try to re-sort the list as this would be an expensive action
* and would result in inconsistencies between clients.
*
* To get information about each entry, use e.g. dc_chatlist_get_summary().
* To get information about each entry, use eg. dc_chatlist_get_summary().
*
* By default, the function adds some special entries to the list.
* These special entries can be identified by the ID returned by dc_chatlist_get_chat_id():
@@ -664,7 +670,7 @@ int dc_preconfigure_keypair (dc_context_t* context, const cha
* typically used on forwarding, may be combined with DC_GCL_NO_SPECIALS
* to also hide the archive link.
* - if the flag DC_GCL_NO_SPECIALS is set, deaddrop and archive link are not added
* to the list (may be used e.g. for selecting chats on forwarding, the flag is
* to the list (may be used eg. for selecting chats on forwarding, the flag is
* not needed when DC_GCL_ARCHIVED_ONLY is already set)
* - if the flag DC_GCL_ADD_ALLDONE_HINT is set, DC_CHAT_ID_ALLDONE_HINT
* is added as needed.
@@ -716,7 +722,7 @@ uint32_t dc_create_chat_by_msg_id (dc_context_t* context, uint32_t ms
* see dc_create_group_chat().
*
* If a chat already exists, this ID is returned, otherwise a new chat is created;
* this new chat may already contain messages, e.g. from the deaddrop, to get the
* this new chat may already contain messages, eg. from the deaddrop, to get the
* chat messages, use dc_get_chat_msgs().
*
* @memberof dc_context_t
@@ -795,7 +801,7 @@ uint32_t dc_prepare_msg (dc_context_t* context, uint32_t ch
*
* Sends the event #DC_EVENT_MSGS_CHANGED on succcess.
* However, this does not imply, the message really reached the recipient -
* sending may be delayed e.g. due to network problems. However, from your
* sending may be delayed eg. due to network problems. However, from your
* view, you're done with the message. Sooner or later it will find its way.
*
* Example:
@@ -852,7 +858,7 @@ uint32_t dc_send_msg_sync (dc_context_t* context, uint32
*
* Sends the event #DC_EVENT_MSGS_CHANGED on succcess.
* However, this does not imply, the message really reached the recipient -
* sending may be delayed e.g. due to network problems. However, from your
* sending may be delayed eg. due to network problems. However, from your
* view, you're done with the message. Sooner or later it will find its way.
*
* See also dc_send_msg().
@@ -883,7 +889,7 @@ uint32_t dc_send_text_msg (dc_context_t* context, uint32_t ch
* and a url that can be opened in a supported browser to join the videochat
*
* - delta-clients can get all information needed from
* the message object, using e.g.
* the message object, using eg.
* dc_msg_get_videochat_url() and check dc_msg_get_viewtype() for #DC_MSG_VIDEOCHAT_INVITATION
*
* dc_send_videochat_invitation() is blocking and may take a while,
@@ -892,9 +898,9 @@ uint32_t dc_send_text_msg (dc_context_t* context, uint32_t ch
* for this purpose, the function returns the message-id directly.
*
* As for other messages sent, this function
* sends the event #DC_EVENT_MSGS_CHANGED on success, the message has a delivery state, and so on.
* sends the event #DC_EVENT_MSGS_CHANGED on succcess, the message has a delivery state, and so on.
* The recipient will get noticed by the call as usual by #DC_EVENT_INCOMING_MSG or #DC_EVENT_MSGS_CHANGED,
* However, UIs might some things differently, e.g. play a different sound.
* However, UIs might some things differently, eg. play a different sound.
*
* @memberof dc_context_t
* @param context The context object
@@ -915,7 +921,7 @@ uint32_t dc_send_videochat_invitation (dc_context_t* context, uint32_t chat_id);
* allowing the user to continue editing and sending.
*
* Drafts are considered when sorting messages
* and are also returned e.g. by dc_chatlist_get_summary().
* and are also returned eg. by dc_chatlist_get_summary().
*
* Each chat can have its own draft but only one draft per chat is possible.
*
@@ -1163,7 +1169,7 @@ uint32_t dc_get_next_media (dc_context_t* context, uint32_t ms
* @param context The context object as returned from dc_context_new().
* @param chat_id The ID of the chat to change the protection for.
* @param protect 1=protect chat, 0=unprotect chat
* @return 1=success, 0=error, e.g. some members may be unverified
* @return 1=success, 0=error, eg. some members may be unverified
*/
int dc_set_chat_protection (dc_context_t* context, uint32_t chat_id, int protect);
@@ -1418,7 +1424,7 @@ int dc_set_chat_profile_image (dc_context_t* context, uint32_t ch
/**
* Set mute duration of a chat.
*
* The UI can then call dc_chat_is_muted() when receiving a new message to decide whether it should trigger an notification.
* The ui can then call dc_chat_is_muted() when receiving a new message to decide whether it should trigger an notification.
*
* Sends out #DC_EVENT_CHAT_MODIFIED.
*
@@ -1434,7 +1440,7 @@ int dc_set_chat_mute_duration (dc_context_t* context, ui
/**
* Get an informational text for a single message. The text is multiline and may
* contain e.g. the raw text of the message.
* contain eg. the raw text of the message.
*
* The max. text returned is typically longer (about 100000 characters) than the
* max. text returned by dc_msg_get_text() (about 30000 characters).
@@ -1458,7 +1464,7 @@ char* dc_get_msg_info (dc_context_t* context, uint32_t ms
* @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,
* e.g. because of save_mime_headers is not set
* eg. because of save_mime_headers is not set
* or the message is not incoming.
*/
char* dc_get_mime_headers (dc_context_t* context, uint32_t msg_id);
@@ -1506,7 +1512,7 @@ void dc_marknoticed_contact (dc_context_t* context, uint32_t co
/**
* Mark a message as _seen_, updates the IMAP state and
* sends MDNs. If the message is not in a real chat (e.g. a contact request), the
* sends MDNs. If the message is not in a real chat (eg. a contact request), the
* message is only marked as NOTICED and no IMAP/MDNs is done. See also
* dc_marknoticed_chat() and dc_marknoticed_contact()
*
@@ -1680,7 +1686,7 @@ void dc_block_contact (dc_context_t* context, uint32_t co
/**
* Get encryption info for a contact.
* Get a multi-line encryption info, containing your fingerprint and the
* fingerprint of the contact, used e.g. to compare the fingerprints for a simple out-of-band verification.
* 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.
@@ -1705,7 +1711,7 @@ int dc_delete_contact (dc_context_t* context, uint32_t co
/**
* Get a single contact object. For a list, see e.g. dc_get_contacts().
* Get a single contact object. For a list, see eg. dc_get_contacts().
*
* For contact DC_CONTACT_ID_SELF (1), the function returns sth.
* like "Me" in the selected language and the email address
@@ -1774,7 +1780,7 @@ void dc_imex (dc_context_t* context, int what, c
/**
* Check if there is a backup file.
* May only be used on fresh installations (e.g. dc_is_configured() returns 0).
* May only be used on fresh installations (eg. dc_is_configured() returns 0).
*
* Example:
*
@@ -1867,7 +1873,7 @@ char* dc_imex_has_backup (dc_context_t* context, const char*
* @memberof dc_context_t
* @param context The context object.
* @return The setup code. Must be released using dc_str_unref() after usage.
* On errors, e.g. if the message could not be sent, NULL is returned.
* On errors, eg. if the message could not be sent, NULL is returned.
*/
char* dc_initiate_key_transfer (dc_context_t* context);
@@ -1890,7 +1896,7 @@ char* dc_initiate_key_transfer (dc_context_t* context);
* There is no need to format the string correctly, the function will remove all spaces and other characters and
* insert the `-` characters at the correct places.
* @return 1=key successfully decrypted and imported; both devices will use the same key now;
* 0=key transfer failed e.g. due to a bad setup code.
* 0=key transfer failed eg. due to a bad setup code.
*/
int dc_continue_key_transfer (dc_context_t* context, uint32_t msg_id, const char* setup_code);
@@ -1961,7 +1967,7 @@ dc_lot_t* dc_check_qr (dc_context_t* context, const char*
/**
* Get QR code text that will offer an Setup-Contact or Verified-Group invitation.
* The QR code is compatible to the OPENPGP4FPR format
* so that a basic fingerprint comparison also works e.g. with OpenKeychain.
* so that a basic fingerprint comparison also works eg. with OpenKeychain.
*
* The scanning device will pass the scanned content to dc_check_qr() then;
* if dc_check_qr() returns DC_QR_ASK_VERIFYCONTACT or DC_QR_ASK_VERIFYGROUP
@@ -2242,7 +2248,7 @@ void dc_accounts_unref (dc_accounts_t* accounts);
* Internally, dc_context_new() is called using a unique database-name
* in the directory specified at dc_accounts_new().
*
* If the function succeeds,
* 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.
@@ -2271,7 +2277,7 @@ uint32_t dc_accounts_import_account (dc_accounts_t* accounts, const
/**
* Migrate independent accounts into accounts managed by the account manager.
* 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_
@@ -2295,10 +2301,9 @@ uint32_t dc_accounts_migrate_account (dc_accounts_t* accounts, const
*
* @memberof dc_accounts_t
* @param accounts Account manager as created by dc_accounts_new().
* @param account_id The account-id as returned e.g. by dc_accounts_add_account().
* @return 1=success, 0=error
*/
int dc_accounts_remove_account (dc_accounts_t* accounts, uint32_t account_id);
int dc_accounts_remove_account (dc_accounts_t* accounts, uint32_t);
/**
@@ -2317,7 +2322,7 @@ dc_array_t* dc_accounts_get_all (dc_accounts_t* accounts);
*
* @memberof dc_accounts_t
* @param accounts Account manager as created by dc_accounts_new().
* @param account_id The account-id as returned e.g. by dc_accounts_get_all() or dc_accounts_add_account().
* @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,
@@ -2348,7 +2353,7 @@ dc_context_t* dc_accounts_get_selected_account (dc_accounts_t* accounts);
*
* @memberof dc_accounts_t
* @param accounts Account manager as created by dc_accounts_new().
* @param account_id The account-id as returned e.g. by dc_accounts_get_all() or dc_accounts_add_account().
* @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);
@@ -2368,6 +2373,7 @@ 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.
*
@@ -2426,7 +2432,7 @@ dc_accounts_event_emitter_t* dc_accounts_get_event_emitter (dc_accounts_t* accou
*
* @memberof dc_array_t
* @param array The array object to free,
* created e.g. by dc_get_chatlist(), dc_get_contacts() and so on.
* created eg. by dc_get_chatlist(), dc_get_contacts() and so on.
* If NULL is given, nothing is done.
*/
void dc_array_unref (dc_array_t* array);
@@ -2624,7 +2630,7 @@ int dc_array_search_id (const dc_array_t* array, uint32_t
* Free a chatlist object.
*
* @memberof dc_chatlist_t
* @param chatlist The chatlist object to free, created e.g. by dc_get_chatlist(), dc_search_msgs().
* @param chatlist The chatlist object to free, created eg. by dc_get_chatlist(), dc_search_msgs().
* If NULL is given, nothing is done.
*/
void dc_chatlist_unref (dc_chatlist_t* chatlist);
@@ -2634,7 +2640,7 @@ void dc_chatlist_unref (dc_chatlist_t* chatlist);
* Find out the number of chats in a chatlist.
*
* @memberof dc_chatlist_t
* @param chatlist The chatlist object as created e.g. by dc_get_chatlist().
* @param chatlist The chatlist object as created eg. by dc_get_chatlist().
* @return Returns the number of items in a dc_chatlist_t object. 0 on errors or if the list is empty.
*/
size_t dc_chatlist_get_cnt (const dc_chatlist_t* chatlist);
@@ -2646,7 +2652,7 @@ size_t dc_chatlist_get_cnt (const dc_chatlist_t* chatlist);
* To get the message object from the message ID, use dc_get_chat().
*
* @memberof dc_chatlist_t
* @param chatlist The chatlist object as created e.g. by dc_get_chatlist().
* @param chatlist The chatlist object as created eg. by dc_get_chatlist().
* @param index The index to get the chat ID for.
* @return Returns the chat_id of the item at the given index. Index must be between
* 0 and dc_chatlist_get_cnt()-1.
@@ -2660,10 +2666,10 @@ uint32_t dc_chatlist_get_chat_id (const dc_chatlist_t* chatlist, siz
* To get the message object from the message ID, use dc_get_msg().
*
* @memberof dc_chatlist_t
* @param chatlist The chatlist object as created e.g. by dc_get_chatlist().
* @param chatlist The chatlist object as created eg. by dc_get_chatlist().
* @param index The index to get the chat ID for.
* @return Returns the message_id of the item at the given index. Index must be between
* 0 and dc_chatlist_get_cnt()-1. If there is no message at the given index (e.g. the chat may be empty), 0 is returned.
* 0 and dc_chatlist_get_cnt()-1. If there is no message at the given index (eg. the chat may be empty), 0 is returned.
*/
uint32_t dc_chatlist_get_msg_id (const dc_chatlist_t* chatlist, size_t index);
@@ -2681,14 +2687,14 @@ uint32_t dc_chatlist_get_msg_id (const dc_chatlist_t* chatlist, siz
* Typically used to show dc_lot_t::text1 with different colors. 0 if not applicable.
*
* - dc_lot_t::text2: contains an excerpt of the message text or strings as
* "No messages". May be NULL of there is no such text (e.g. for the archive link)
* "No messages". May be NULL of there is no such text (eg. for the archive link)
*
* - dc_lot_t::timestamp: the timestamp of the message. 0 if not applicable.
*
* - dc_lot_t::state: The state of the message as one of the DC_STATE_* constants (see #dc_msg_get_state()). 0 if not applicable.
*
* @memberof dc_chatlist_t
* @param chatlist The chatlist to query as returned e.g. from dc_get_chatlist().
* @param chatlist The chatlist to query as returned eg. from dc_get_chatlist().
* @param index The index to query in the chatlist.
* @param chat To speed up things, pass an already available chat object here.
* If the chat object is not yet available, it is faster to pass NULL.
@@ -2705,7 +2711,7 @@ dc_lot_t* dc_chatlist_get_summary (const dc_chatlist_t* chatlist, siz
* as arguments. The chatlist object itself is not needed directly.
*
* This maybe useful if you convert the complete object into a different represenation
* as done e.g. in the node-bindings.
* as done eg. in the node-bindings.
* If you have access to the chatlist object in some way, using this function is not recommended,
* use dc_chatlist_get_summary() in this case instead.
*
@@ -2730,9 +2736,9 @@ dc_context_t* dc_chatlist_get_context (dc_chatlist_t* chatlist);
/**
* Get info summary for a chat, in JSON format.
* Get info summary for a chat, in json format.
*
* The returned JSON string has the following key/values:
* The returned json string has the following key/values:
*
* id: chat id
* name: chat/group name
@@ -2743,7 +2749,7 @@ dc_context_t* dc_chatlist_get_context (dc_chatlist_t* chatlist);
* last-message-date:
* avatar-path: path-to-blobfile
* is_verified: yes/no
* @return a UTF8-encoded JSON string containing all requested info. Must be freed using dc_str_unref(). NULL is never returned.
* @return a utf8-encoded json string containing all requested info. Must be freed using dc_str_unref(). NULL is never returned.
*/
char* dc_chat_get_info_json (dc_context_t* context, size_t chat_id);
@@ -2751,7 +2757,7 @@ char* dc_chat_get_info_json (dc_context_t* context, size_t chat
* @class dc_chat_t
*
* An object representing a single chat in memory.
* Chat objects are created using e.g. dc_get_chat()
* Chat objects are created using eg. dc_get_chat()
* and are not updated on database changes;
* if you want an update, you have to recreate the object.
*/
@@ -2773,7 +2779,7 @@ char* dc_chat_get_info_json (dc_context_t* context, size_t chat
* Free a chat object.
*
* @memberof dc_chat_t
* @param chat Chat object are returned e.g. by dc_get_chat().
* @param chat Chat object are returned eg. by dc_get_chat().
* If NULL is given, nothing is done.
*/
void dc_chat_unref (dc_chat_t* chat);
@@ -2816,7 +2822,7 @@ int dc_chat_get_type (const dc_chat_t* chat);
/**
* Get name of a chat. For one-to-one chats, this is the name of the contact.
* For group chats, this is the name given e.g. to dc_create_group_chat() or
* For group chats, this is the name given eg. to dc_create_group_chat() or
* received by a group-creation message.
*
* To change the name, use dc_set_chat_name()
@@ -2909,7 +2915,7 @@ int dc_chat_is_self_talk (const dc_chat_t* chat);
* Device-talks contain update information
* and some hints that are added during the program runs, multi-device etc.
*
* From the UI view, device-talks are not very special,
* From the ui view, device-talks are not very special,
* the user can delete and forward messages, archive the chat, set notifications etc.
*
* Messages can be added to the device-talk using dc_add_device_msg()
@@ -2923,10 +2929,10 @@ int dc_chat_is_device_talk (const dc_chat_t* chat);
/**
* Check if messages can be sent to a give chat.
* This is not true e.g. for the deaddrop or for the device-talk, cmp. dc_chat_is_device_talk().
* This is not true eg. for the deaddrop or for the device-talk, cmp. dc_chat_is_device_talk().
*
* Calling dc_send_msg() for these chats will fail
* and the UI may decide to hide input controls therefore.
* and the ui may decide to hide input controls therefore.
*
* @memberof dc_chat_t
* @param chat The chat object.
@@ -3012,8 +3018,8 @@ int64_t dc_chat_get_remaining_mute_duration (const dc_chat_t* chat);
/**
* Create new message object. Message objects are needed e.g. for sending messages using
* dc_send_msg(). Moreover, they are returned e.g. from dc_get_msg(),
* Create new message object. Message objects are needed eg. for sending messages using
* dc_send_msg(). Moreover, they are returned eg. from dc_get_msg(),
* set up with the current state of a message. The message object is not updated;
* to achieve this, you have to recreate it.
*
@@ -3027,7 +3033,7 @@ dc_msg_t* dc_msg_new (dc_context_t* context, int viewty
/**
* Free a message object. Message objects are created e.g. by dc_get_msg().
* Free a message object. Message objects are created eg. by dc_get_msg().
*
* @memberof dc_msg_t
* @param msg The message object to free.
@@ -3109,12 +3115,12 @@ int dc_msg_get_viewtype (const dc_msg_t* msg);
* If a sent message changes to this state, you'll receive the event #DC_EVENT_MSG_READ.
* Also messages already read by some recipients
* may get into the state DC_STATE_OUT_FAILED at a later point,
* e.g. when in a group, delivery fails for some recipients.
* eg. when in a group, delivery fails for some recipients.
*
* If you just want to check if a message is sent or not, please use dc_msg_is_sent() which regards all states accordingly.
*
* The state of just created message objects is DC_STATE_UNDEFINED (0).
* The state is always set by the core-library, users of the library cannot set the state directly, but it is changed implicitly e.g.
* The state is always set by the core-library, users of the library cannot set the state directly, but it is changed implicitly eg.
* when calling dc_marknoticed_chat() or dc_markseen_msgs().
*
* @memberof dc_msg_t
@@ -3128,7 +3134,7 @@ int dc_msg_get_state (const dc_msg_t* msg);
* Get message sending time.
* The sending time is returned as a unix timestamp in seconds.
*
* Note that the message lists returned e.g. by dc_get_chat_msgs()
* Note that the message lists returned eg. by dc_get_chat_msgs()
* are not sorted by the _sending_ time but by the _receiving_ time.
* This ensures newly received messages always pop up at the end of the list,
* however, for delayed messages, the correct sending time will be displayed.
@@ -3160,7 +3166,7 @@ int64_t dc_msg_get_received_timestamp (const dc_msg_t* msg);
/**
* Get message time used for sorting.
* This function returns the timestamp that is used for sorting the message
* into lists as returned e.g. by dc_get_chat_msgs().
* into lists as returned eg. by dc_get_chat_msgs().
* This may be the reveived time, the sending time or another time.
*
* To get the receiving time, use dc_msg_get_received_timestamp().
@@ -3183,7 +3189,7 @@ int64_t dc_msg_get_sort_timestamp (const dc_msg_t* msg);
* it does not make sense to show more text in the message list and typical controls
* will have problems with showing much more text.
* This max. length is to avoid passing _lots_ of data to the frontend which may
* result e.g. from decoding errors (assume some bytes missing in a mime structure, forcing
* result eg. from decoding errors (assume some bytes missing in a mime structure, forcing
* an attachment to be plain text).
*
* To get information about the message and more/raw text, use dc_get_msg_info().
@@ -3241,7 +3247,7 @@ char* dc_msg_get_filemime (const dc_msg_t* msg);
* Get the size of the file. Returns the size of the file associated with a
* message, if applicable.
*
* Typically, this is used to show the size of document messages, e.g. a PDF.
* Typically, this is used to show the size of document messages, eg. a PDF.
*
* @memberof dc_msg_t
* @param msg The message object.
@@ -3440,7 +3446,7 @@ int dc_msg_is_forwarded (const dc_msg_t* msg);
* Check if the message is an informational message, created by the
* device or by another users. Such messages are not "typed" by the user but
* created due to other actions,
* e.g. dc_set_chat_name(), dc_set_chat_profile_image(), dc_set_chat_protection()
* eg. dc_set_chat_name(), dc_set_chat_profile_image(), dc_set_chat_protection()
* or dc_add_contact_to_chat().
*
* These messages are typically shown in the center of the chat view,
@@ -3459,7 +3465,7 @@ int dc_msg_is_info (const dc_msg_t* msg);
/**
* Get the type of an informational message.
* If dc_msg_is_info() returns 1, this function returns the type of the informational message.
* UIs can display e.g. an icon based upon the type.
* UIs can display eg. an icon based upon the type.
*
* Currently, the following types are defined:
* - DC_INFO_PROTECTION_ENABLED (11) - Info-message for "Chat is now protected"
@@ -3500,7 +3506,7 @@ int dc_msg_is_increation (const dc_msg_t* msg);
/**
* Check if the message is an Autocrypt Setup Message.
*
* Setup messages should be shown in an unique way e.g. using a different text color.
* Setup messages should be shown in an unique way eg. using a different text color.
* On a click or another action, the user should be prompted for the setup code
* which is forwarded to dc_continue_key_transfer() then.
*
@@ -3742,7 +3748,7 @@ char* dc_msg_get_quoted_text (const dc_msg_t* msg);
* To check if a message has a quote, use dc_msg_get_quoted_text().
*
* To display the quote in the chat, use dc_msg_get_quoted_text() as a primary source,
* however, one might add information from the message object (e.g. an image).
* however, one might add information from the message object (eg. an image).
*
* It is not guaranteed that the message belong to the same chat.
*
@@ -3768,7 +3774,7 @@ dc_msg_t* dc_msg_get_quoted_msg (const dc_msg_t* msg);
* authorized-name and given-name.
* By default, these names are equal,
* but functions working with contact names
* (e.g. dc_contact_get_name(), dc_contact_get_display_name(),
* (eg. dc_contact_get_name(), dc_contact_get_display_name(),
* dc_contact_get_name_n_addr(), dc_contact_get_first_name(),
* dc_create_contact() or dc_add_address_book())
* only affect the given-name.
@@ -3785,7 +3791,7 @@ dc_msg_t* dc_msg_get_quoted_msg (const dc_msg_t* msg);
* Free a contact object.
*
* @memberof dc_contact_t
* @param contact The contact object as created e.g. by dc_get_contact().
* @param contact The contact object as created eg. by dc_get_contact().
* If NULL is given, nothing is done.
*/
void dc_contact_unref (dc_contact_t* contact);
@@ -3849,7 +3855,7 @@ char* dc_contact_get_display_name (const dc_contact_t* contact);
* "email@domain.com" if the name is unset.
*
* The summary is typically used when asking the user something about the contact.
* The attached email address makes the question unique, e.g. "Chat with Alan Miller (am@uniquedomain.com)?"
* The attached email address makes the question unique, eg. "Chat with Alan Miller (am@uniquedomain.com)?"
*
* @memberof dc_contact_t
* @param contact The contact object.
@@ -3965,9 +3971,9 @@ char* dc_provider_get_overview_page (const dc_provider_t* prov
/**
* Get hints to be shown to the user on the login screen.
* Depending on the @ref DC_PROVIDER_STATUS returned by dc_provider_get_status(),
* the UI may want to highlight the hint.
* the ui may want to highlight the hint.
*
* Moreover, the UI should display a "More information" link
* Moreover, the ui should display a "More information" link
* that forwards to the url returned by dc_provider_get_overview_page().
*
* @memberof dc_provider_t
@@ -4007,7 +4013,7 @@ void dc_provider_unref (dc_provider_t* provider);
* An object containing a set of values.
* The meaning of the values is defined by the function returning the object.
* Lot objects are created
* e.g. by dc_chatlist_get_summary() or dc_msg_get_summary().
* eg. by dc_chatlist_get_summary() or dc_msg_get_summary().
*
* NB: _Lot_ is used in the meaning _heap_ here.
*/
@@ -4021,7 +4027,7 @@ void dc_provider_unref (dc_provider_t* provider);
/**
* Frees an object containing a set of parameters.
* If the set object contains strings, the strings are also freed with this function.
* Set objects are created e.g. by dc_chatlist_get_summary() or dc_msg_get_summary().
* Set objects are created eg. by dc_chatlist_get_summary() or dc_msg_get_summary().
*
* @memberof dc_lot_t
* @param lot The object to free.
@@ -4055,7 +4061,7 @@ char* dc_lot_get_text2 (const dc_lot_t* lot);
/**
* Get the meaning of the first string. Posssible meanings of the string are defined by the creator of the object and may be returned e.g.
* Get the meaning of the first string. Posssible meanings of the string are defined by the creator of the object and may be returned eg.
* as DC_TEXT1_DRAFT, DC_TEXT1_USERNAME or DC_TEXT1_SELF.
*
* @memberof dc_lot_t
@@ -4105,9 +4111,9 @@ int64_t dc_lot_get_timestamp (const dc_lot_t* lot);
*
* From the view of the library,
* all types are primary types of the same level,
* e.g. the library does not regard #DC_MSG_GIF as a subtype for #DC_MSG_IMAGE
* eg. the library does not regard #DC_MSG_GIF as a subtype for #DC_MSG_IMAGE
* and it's up to the UI to decide whether a GIF is shown
* e.g. in an IMAGE or in a VIDEO container.
* eg. in an IMAGE or in a VIDEO container.
*
* If you want to define the type of a dc_msg_t object for sending,
* use dc_msg_new().
@@ -4146,7 +4152,7 @@ int64_t dc_lot_get_timestamp (const dc_lot_t* lot);
/**
* Message containing a sticker, similar to image.
* If possible, the UI should display the image without borders in a transparent way.
* If possible, the ui should display the image without borders in a transparent way.
* A click on a sticker will offer to install the sticker set in some future.
*/
#define DC_MSG_STICKER 23
@@ -4181,7 +4187,7 @@ int64_t dc_lot_get_timestamp (const dc_lot_t* lot);
/**
* Message containing any file, e.g. a PDF.
* Message containing any file, eg. a PDF.
* The file is set via dc_msg_set_file()
* and retrieved via dc_msg_get_file().
*/
@@ -4193,7 +4199,7 @@ int64_t dc_lot_get_timestamp (const dc_lot_t* lot);
* The message was created via dc_send_videochat_invitation() on this or a remote device.
*
* Typically, such messages are rendered differently by the UIs,
* e.g. contain a button to join the videochat.
* eg. contain a button to join the videochat.
* The url for joining can be retrieved using dc_msg_get_videochat_url().
*/
#define DC_MSG_VIDEOCHAT_INVITATION 70
@@ -4327,7 +4333,7 @@ int64_t dc_lot_get_timestamp (const dc_lot_t* lot);
* @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 no more events will come;
* 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_event_emitter_unref().
*/
dc_event_t* dc_get_next_event(dc_event_emitter_t* emitter);
@@ -4360,7 +4366,7 @@ void dc_event_emitter_unref(dc_event_emitter_t* 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 contexts belonging to the event emitter are unref'd and no more events will come;
* 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);
@@ -4482,7 +4488,7 @@ void dc_event_unref(dc_event_t* event);
* This event should not be reported to the end-user using a popup or something like that.
*
* @param data1 0
* @param data2 (char*) Info string in English language.
* @param data2 (char*) Info string in english language.
*/
#define DC_EVENT_INFO 100
@@ -4491,7 +4497,7 @@ void dc_event_unref(dc_event_t* event);
* Emitted when SMTP connection is established and login was successful.
*
* @param data1 0
* @param data2 (char*) Info string in English language.
* @param data2 (char*) Info string in english language.
*/
#define DC_EVENT_SMTP_CONNECTED 101
@@ -4500,7 +4506,7 @@ void dc_event_unref(dc_event_t* event);
* Emitted when IMAP connection is established and login was successful.
*
* @param data1 0
* @param data2 (char*) Info string in English language.
* @param data2 (char*) Info string in english language.
*/
#define DC_EVENT_IMAP_CONNECTED 102
@@ -4508,7 +4514,7 @@ void dc_event_unref(dc_event_t* event);
* Emitted when a message was successfully sent to the SMTP server.
*
* @param data1 0
* @param data2 (char*) Info string in English language.
* @param data2 (char*) Info string in english language.
*/
#define DC_EVENT_SMTP_MESSAGE_SENT 103
@@ -4516,7 +4522,7 @@ void dc_event_unref(dc_event_t* event);
* Emitted when a message was successfully marked as deleted on the IMAP server.
*
* @param data1 0
* @param data2 (char*) Info string in English language.
* @param data2 (char*) Info string in english language.
*/
#define DC_EVENT_IMAP_MESSAGE_DELETED 104
@@ -4524,7 +4530,7 @@ void dc_event_unref(dc_event_t* event);
* Emitted when a message was successfully moved on IMAP.
*
* @param data1 0
* @param data2 (char*) Info string in English language.
* @param data2 (char*) Info string in english language.
*/
#define DC_EVENT_IMAP_MESSAGE_MOVED 105
@@ -4550,7 +4556,7 @@ void dc_event_unref(dc_event_t* event);
* This event should not be reported to the end-user using a popup or something like that.
*
* @param data1 0
* @param data2 (char*) Warning string in English language.
* @param data2 (char*) Warning string in english language.
*/
#define DC_EVENT_WARNING 300
@@ -4561,16 +4567,16 @@ void dc_event_unref(dc_event_t* event);
* As most things are asynchronous, things may go wrong at any time and the user
* should not be disturbed by a dialog or so. Instead, use a bubble or so.
*
* However, for ongoing processes (e.g. dc_configure())
* or for functions that are expected to fail (e.g. dc_continue_key_transfer())
* However, for ongoing processes (eg. dc_configure())
* or for functions that are expected to fail (eg. dc_continue_key_transfer())
* it might be better to delay showing these events until the function has really
* failed (returned false). It should be sufficient to report only the _last_ error
* in a message box then.
* in a messasge box then.
*
* @param data1 0
* @param data2 (char*) Error string, always set, never NULL.
* Some error strings are taken from dc_set_stock_translation(),
* however, most error strings will be in English language.
* however, most error strings will be in english language.
*/
#define DC_EVENT_ERROR 400
@@ -4597,13 +4603,13 @@ void dc_event_unref(dc_event_t* event);
/**
* An action cannot be performed because the user is not in the group.
* Reported e.g. after a call to
* Reported eg. after a call to
* dc_set_chat_name(), dc_set_chat_profile_image(),
* dc_add_contact_to_chat(), dc_remove_contact_from_chat(),
* dc_send_text_msg() or another sending function.
*
* @param data1 0
* @param data2 (char*) Info string in English language.
* @param data2 (char*) Info string in english language.
*/
#define DC_EVENT_ERROR_SELF_NOT_IN_GROUP 410
@@ -4635,12 +4641,12 @@ void dc_event_unref(dc_event_t* event);
/**
* Messages were marked noticed or seen.
* The UI may update badge counters or stop showing a chatlist-item with a bold font.
* The ui may update badge counters or stop showing a chatlist-item with a bold font.
*
* This event is emitted e.g. when calling dc_markseen_msgs(), dc_marknoticed_chat() or dc_marknoticed_contact()
* This event is emitted eg. when calling dc_markseen_msgs(), dc_marknoticed_chat() or dc_marknoticed_contact()
* or when a chat is answered on another device.
* Do not try to derive the state of an item from just the fact you received the event;
* use e.g. dc_msg_get_state() or dc_get_fresh_msg_cnt() for this purpose.
* use eg. dc_msg_get_state() or dc_get_fresh_msg_cnt() for this purpose.
*
* @param data1 (int) chat_id
* @param data2 0
@@ -4711,7 +4717,7 @@ void dc_event_unref(dc_event_t* event);
*
* @param data1 (int) contact_id of the contact for which the location has changed.
* If the locations of several contacts have been changed,
* e.g. after calling dc_delete_all_locations(), this parameter is set to 0.
* eg. after calling dc_delete_all_locations(), this parameter is set to 0.
* @param data2 0
*/
#define DC_EVENT_LOCATION_CHANGED 2035
@@ -4826,7 +4832,7 @@ void dc_event_unref(dc_event_t* event);
* works by just entering the name or the email-address.
*
* - There is no need for the user to do any special things
* (enable IMAP or so) in the provider's web interface or at other places.
* (enable IMAP or so) in the provider's webinterface or at other places.
* - There is no need for the user to enter advanced settings;
* server, port etc. are known by the core.
*
@@ -4837,8 +4843,8 @@ void dc_event_unref(dc_event_t* event);
/**
* Provider works, but there are preparations needed.
*
* - The user has to do some special things as "Enable IMAP in the web interface",
* what exactly, is described in the string returned by dc_provider_get_before_login_hints()
* - The user has to do some special things as "Enable IMAP in the Webinterface",
* what exactly, is described in the string returnd by dc_provider_get_before_login_hints()
* and, typically more detailed, in the page linked by dc_provider_get_overview_page().
* - There is no need for the user to enter advanced settings;
* server, port etc. should be known by the core.
@@ -4851,7 +4857,7 @@ void dc_event_unref(dc_event_t* event);
* Provider is not working.
* This provider status is returned for providers
* that are known to not work with Delta Chat.
* The UI should block logging in with this provider.
* The ui should block logging in with this provider.
*
* More information about that is typically provided
* in the string returned by dc_provider_get_before_login_hints()
@@ -4886,10 +4892,10 @@ void dc_event_unref(dc_event_t* event);
/**
* Archived chats are not included in the default chatlist returned by dc_get_chatlist().
* Instead, if there are _any_ archived chats, the pseudo-chat
* with the chat_id DC_CHAT_ID_ARCHIVED_LINK will be added at the end of the chatlist.
* with the chat_id DC_CHAT_ID_ARCHIVED_LINK will be added the the end of the chatlist.
*
* The UI typically shows a little icon or chats beside archived chats in the chatlist,
* this is needed as e.g. the search will also return archived chats.
* this is needed as eg. the search will also return archived chats.
*
* If archived chats receive new messages, they become normal chats again.
*
@@ -4909,7 +4915,7 @@ void dc_event_unref(dc_event_t* event);
/*
* TODO: Strings need some documentation about used placeholders.
* TODO: Strings need some doumentation about used placeholders.
*
* @defgroup DC_STR DC_STR
*
@@ -4981,8 +4987,7 @@ void dc_event_unref(dc_event_t* event);
#define DC_STR_ERROR_NO_NETWORK 87
#define DC_STR_PROTECTION_ENABLED 88
#define DC_STR_PROTECTION_DISABLED 89
#define DC_STR_REPLY_NOUN 90 /* e.g. "Reply", used in summaries, a noun, not a verb (not: "to reply") */
#define DC_STR_SELF_DELETED_MSG_BODY 91
#define DC_STR_REPLY_NOUN 90 /* eg. "Reply", used in summaries, a noun, not a verb (not: "to reply") */
/*
* @}

View File

@@ -1727,15 +1727,13 @@ pub unsafe extern "C" fn dc_imex(
let ctx = &*context;
if let Some(param1) = to_opt_string_lossy(param1) {
spawn(async move {
imex::imex(&ctx, what, &param1)
.await
.log_err(ctx, "IMEX failed")
});
} else {
eprintln!("dc_imex called without a valid directory");
}
let param1 = to_opt_string_lossy(param1);
spawn(async move {
imex::imex(&ctx, what, param1)
.await
.log_err(ctx, "IMEX failed")
});
}
#[no_mangle]
@@ -3008,11 +3006,6 @@ pub unsafe extern "C" fn dc_msg_set_quote(msg: *mut dc_msg_t, quote: *const dc_m
let ffi_msg = &mut *msg;
let ffi_quote = &*quote;
if ffi_msg.context != ffi_quote.context {
eprintln!("ignoring attempt to quote message from a different context");
return;
}
block_on(async move {
ffi_msg
.message

View File

@@ -5,95 +5,69 @@ Problem: missing eventual group consistency
If group members are concurrently adding new members,
the new members will miss each other's additions, example:
1. Alice and Bob are in a two-member group
- Alice and Bob are in a two-member group
2. Then Alice adds Carol, while concurrently Bob adds Doris
- Alice adds Carol, concurrently Bob adds Doris
Right now, the group has inconsistent memberships:
- Carol will see a three-member group (Alice, Bob, Carol),
Doris will see a different three-member group (Alice, Bob, Doris),
and only Alice and Bob will have all four members.
- Alice and Carol see a (Alice, Carol, Bob) group
- Bob and Doris see a (Bob, Doris, Alice)
This then leads to "sender is unknown" messages in the chat,
for example when Alice receives a message from Doris,
or when Bob receives a message from Carol.
There are also other sources for group membership inconsistency:
- leaving/deleting/adding in larger groups, while being offline,
increases chances for inconsistent group membership
- dropped group-membership messages
- group-membership messages landing in "Spam"
Note that for verified groups any mitigation mechanism likely
needs to make all clients to know who originally added a member.
Note that all these problems (can) also happen with verified groups,
then raising "false alarms" which could lure people to ignore such issues.
solution: memorize+attach (possible encrypted) chat-meta mime messages
----------------------------------------------------------------------
IOW, it's clear we need to do something about it to improve overall
reliability in group-settings.
For reference, please see https://github.com/deltachat/deltachat-core-rust/blob/master/spec.md#add-and-remove-members how MemberAdded/Removed messages are shaped.
- All Chat-Group-Member-Added/Removed messages are recorded in their
full raw (signed and encrypted) mime-format in the DB
Solution: replay group modification messages on inconsistencies
------------------------------------------------------------------
- If an incoming member-add/member-delete messages has a member list
which is, apart from the added/removed member, not consistent
with our own view, broadcast a "Chat-Group-Member-Correction" message to
all members, attaching the original added/removed mime-message for all mismatching
contacts. If we have no relevant add/del information, don't send a
correction message out.
For brevity let's abbreviate "group membership modification" as **GMM**.
- Upong receiving added/removed attachments we don't do the
check_consistency+correction message dance.
This avoids recursion problems and hard-to-reason-about chatter.
Delta chat has explicit GMM messages, typically encrypted to the group members
as seen by the device that sends the GMM. The `Spec <https://github.com/deltachat/deltachat-core-rust/blob/master/spec.md#add-and-remove-members>`_ details the Mime headers and format.
Notes:
If we detect membership inconsistencies we can resend relevant GMM messages
to the respective chat. The receiving devices can process those GMM messages
as if it would be an incoming message. If for example they have already seen
the Message-ID of the GMM message, they will ignore the message. It's
probably useful to record GMM message in their original MIME-format and
not invent a new recording format. Few notes on three aspects:
- mechanism works for both encrypted and unencrypted add/del messages
- **group-membership-tracking**: All valid GMM messages are persisted in
their full raw (signed/encrypted?) MIME-format in the DB. Note that GMM messages
already are in the msgs table, and there is a mime_header column which we could
extend to contain the raw Mime GMM message.
- we already have a "mime_headers" column in the DB for each incoming message.
We could extend it to also include the payload and store mime unconditionally
for member-added/removed messages.
- **consistency_checking**: If an incoming GMM has a member list which is
not consistent with our own view, broadcast a "Group-Member-Correction"
message to all members containing a multipart list of GMMs.
- multiple member-added/removed messages can be attached in a single
correction message
- **correcting_memberships**: Upon receiving a Group-Member-Correction
message we pass the contained GMMs to the "incoming mail pipeline"
(without **consistency_checking** them, to avoid recursion issues)
- it is minimal on the number of overall messages to reach group consistency
(best-case: no extra messages, the ABCD case above: max two extra messages)
- somewhat backward compatible: older clients will probably ignore
messages which are signed by someone who is not the outer From-address.
Alice/Carol and Bob/Doris getting on the same page
++++++++++++++++++++++++++++++++++++++++++++++++++
Recall that Alice/Carol and Bob/Doris had a differening view of
group membership. With the proposed solution, when Bob receives
Alice's "Carol added" message, he will notice that Alice (and thus
also carol) did not know about Doris. Bob's device sends a
"Chat-Group-Member-Correction" message containing his own GMM
when adding Doris. Therefore, the group's membership is healed
for everyone in a single broadcast message.
Alice might also send a Group-member-Correction message,
so there is a second chance that the group gets to know all GMMs.
Note, for example, that if for some reason Bobs and Carols provider
drop GMM messages between them (spam) that Alice and Doris can heal
it by resending GMM messages whenever they detect them to be out of sync.
- the correction-protocol also helps with dropped messages. If a member
did not see a member-added/removed message, the next member add/removed
message in the group will likely heal group consistency for this member.
- we can quite easily extend the mechanism to also provide the group-avatar or
other meta-information.
Discussions of variants
++++++++++++++++++++++++
- instead of acting on GMM messages we could send corrections
for any received message that addresses inconsistent group members but
a) this could delay group-membership healing
- instead of acting on MemberAdded/Removed message we could send
corrections for any received message that addresses inconsistent group members but
a) this would delay group-membership healing
b) could lead to a lot of members sending corrections
c) means we might rely on "To-Addresses" which we also like to strike
at least for protected chats.
- instead of broadcasting correction messages we could only send it to
the sender of the inconsistent member-added/removed message.
@@ -109,3 +83,44 @@ Discussions of variants
while both being in an offline or bad-connection situation).
solution2: repeat member-added/removed messages
---------------------------------------------------
Introduce a new Chat-Group-Member-Changed header and deprecate Chat-Group-Member-Added/Removed
but keep sending out the old headers until the new protocol is sufficiently deployed.
The new Chat-Group-Member-Changed header contains a Time-to-Live number (TTL)
which controls repetition of the signed "add/del e-mail address" payload.
Example::
Chat-Group-Member-Changed: TTL add "somedisplayname" someone@example.org
owEBYQGe/pANAwACAY47A6J5t3LWAcsxYgBeTQypYWRkICJzb21lZGlzcGxheW5h
bWUiIHNvbWVvbmVAZXhhbXBsZS5vcmcgCokBHAQAAQIABgUCXk0MqQAKCRCOOwOi
ebdy1hfRB/wJ74tgFQulicthcv9n+ZsqzwOtBKMEVIHqJCzzDB/Hg/2z8ogYoZNR
iUKKrv3Y1XuFvdKyOC+wC/unXAWKFHYzY6Tv6qDp6r+amt+ad+8Z02q53h9E55IP
FUBdq2rbS8hLGjQB+mVRowYrUACrOqGgNbXMZjQfuV7fSc7y813OsCQgi3tjstup
b+uduVzxCp3PChGhcZPs3iOGCnQvSB8VAaLGMWE2d7nTo/yMQ0Jx69x5qwfXogTk
mTt5rOJyrosbtf09TMKFzGgtqBcEqHLp3+mQpZQ+WHUKAbsRa8Jc9DOUOSKJ8SNM
clKdskprY+4LY0EBwLD3SQ7dPkTITCRD
=P6GG
TTL is set to "2" on an initial Chat-Group-Member-Changed add/del message.
Receivers will apply the add/del change to the group-membership,
decrease the TTL by 1, and if TTL>0 re-sent the header.
The "add|del e-mail address" payload is pgp-signed and repeated verbatim.
This allows to propagate, in a cryptographically secured way,
who added a member. This is particularly important for allowing
to show in verified groups who added a member (planned).
Disadvantage to solution 1:
- requires to specify encoding and precise rules for what/how is signed.
- causes O(N^2) extra messages
- Not easily extendable for other things (without introducing a new
header / encoding)

View File

@@ -443,20 +443,20 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
}
"export-backup" => {
let dir = dirs::home_dir().unwrap_or_default();
imex(&context, ImexMode::ExportBackup, &dir).await?;
imex(&context, ImexMode::ExportBackup, Some(&dir)).await?;
println!("Exported to {}.", dir.to_string_lossy());
}
"import-backup" => {
ensure!(!arg1.is_empty(), "Argument <backup-file> missing.");
imex(&context, ImexMode::ImportBackup, arg1).await?;
imex(&context, ImexMode::ImportBackup, Some(arg1)).await?;
}
"export-keys" => {
let dir = dirs::home_dir().unwrap_or_default();
imex(&context, ImexMode::ExportSelfKeys, &dir).await?;
imex(&context, ImexMode::ExportSelfKeys, Some(&dir)).await?;
println!("Exported to {}.", dir.to_string_lossy());
}
"import-keys" => {
imex(&context, ImexMode::ImportSelfKeys, arg1).await?;
imex(&context, ImexMode::ImportSelfKeys, Some(arg1)).await?;
}
"export-setup" => {
let setup_code = create_setup_code(&context);

View File

@@ -197,12 +197,14 @@ const CHAT_COMMANDS: [&str; 27] = [
"unpin",
"delchat",
];
const MESSAGE_COMMANDS: [&str; 6] = [
const MESSAGE_COMMANDS: [&str; 8] = [
"listmsgs",
"msginfo",
"listfresh",
"forward",
"markseen",
"star",
"unstar",
"delmsg",
];
const CONTACT_COMMANDS: [&str; 6] = [

View File

@@ -214,19 +214,6 @@ class Account(object):
:param name: (optional) display name for this contact
:returns: :class:`deltachat.contact.Contact` instance.
"""
(name, addr) = self.get_contact_addr_and_name(obj, name)
name = as_dc_charpointer(name)
addr = as_dc_charpointer(addr)
contact_id = lib.dc_create_contact(self._dc_context, name, addr)
return Contact(self, contact_id)
def get_contact(self, obj):
if isinstance(obj, Contact):
return obj
(_, addr) = self.get_contact_addr_and_name(obj)
return self.get_contact_by_addr(addr)
def get_contact_addr_and_name(self, obj, name=None):
if isinstance(obj, Account):
if not obj.is_configured():
raise ValueError("can only add addresses from configured accounts")
@@ -242,7 +229,13 @@ class Account(object):
if name is None and displayname:
name = displayname
return (name, addr)
return self._create_contact(addr, name)
def _create_contact(self, addr, name):
addr = as_dc_charpointer(addr)
name = as_dc_charpointer(name)
contact_id = lib.dc_create_contact(self._dc_context, name, addr)
return Contact(self, contact_id)
def delete_contact(self, contact):
""" delete a Contact.

View File

@@ -371,7 +371,7 @@ class Chat(object):
:raises ValueError: if contact could not be removed
:returns: None
"""
contact = self.account.get_contact(obj)
contact = self.account.create_contact(obj)
ret = lib.dc_remove_contact_from_chat(self.account._dc_context, self.id, contact.id)
if ret != 1:
raise ValueError("could not remove contact {!r} from chat".format(contact))

View File

@@ -103,14 +103,6 @@ class FFIEventTracker:
if rex.search(ev.data2):
return ev
def get_info_regex_groups(self, regex, check_error=True):
rex = re.compile(regex)
while 1:
ev = self.get_matching("DC_EVENT_INFO", check_error=check_error)
m = rex.match(ev.data2)
if m is not None:
return m.groups()
def ensure_event_not_queued(self, event_name_regex):
__tracebackhide__ = True
rex = re.compile("(?:{}).*".format(event_name_regex))

View File

@@ -330,13 +330,6 @@ def acfactory(pytestconfig, tmpdir, request, session_liveconfig, data):
return accounts
def clone_online_account(self, account, pre_generated_key=True):
""" Clones addr, mail_pw, mvbox_watch, mvbox_move, sentbox_watch and the
direct_imap object of an online account. This simulates the user setting
up a new device without importing a backup.
`pre_generated_key` only means that a key from python/tests/data/key is
used in order to speed things up.
"""
self.live_count += 1
tmpdb = tmpdir.join("livedb%d" % self.live_count)
ac = self.make_account(tmpdb.strpath, logid="ac{}".format(self.live_count))
@@ -510,9 +503,6 @@ def lp():
def step(self, msg):
print("-" * 5, "step " + msg, "-" * 5)
def indent(self, msg):
print(" " + msg)
return Printer()

View File

@@ -288,28 +288,6 @@ class TestOfflineChat:
qr = chat.get_join_qr()
assert ac2.check_qr(qr).is_ask_verifygroup
def test_removing_blocked_user_from_group(self, ac1, lp):
"""
Test that blocked contact is not unblocked when removed from a group.
See https://github.com/deltachat/deltachat-core-rust/issues/2030
"""
lp.sec("Create a group chat with a contact")
contact = ac1.create_contact("some1@example.org")
group = ac1.create_group_chat("title", contacts=[contact])
group.send_text("First group message")
lp.sec("ac1 blocks contact")
contact.block()
assert contact.is_blocked()
lp.sec("ac1 removes contact from their group")
group.remove_contact(contact)
assert contact.is_blocked()
lp.sec("ac1 adding blocked contact unblocks it")
group.add_contact(contact)
assert not contact.is_blocked()
def test_get_set_profile_image_simple(self, ac1, data):
chat = ac1.create_group_chat(name="title1")
p = data.get_path("d.png")
@@ -668,7 +646,7 @@ class TestOnlineAccount:
except Exception:
pass
def test_export_import_self_keys(self, acfactory, tmpdir, lp):
def test_export_import_self_keys(self, acfactory, tmpdir):
ac1, ac2 = acfactory.get_two_online_accounts()
dir = tmpdir.mkdir("exportdir")
@@ -676,17 +654,8 @@ class TestOnlineAccount:
assert len(export_files) == 2
for x in export_files:
assert x.startswith(dir.strpath)
key_id, = ac1._evtracker.get_info_regex_groups(r".*xporting.*KeyId\((.*)\).*")
ac1._evtracker.consume_events()
lp.sec("exported keys (private and public)")
for name in os.listdir(dir.strpath):
lp.indent(dir.strpath + os.sep + name)
lp.sec("importing into existing account")
ac2.import_self_keys(dir.strpath)
key_id2, = ac2._evtracker.get_info_regex_groups(
r".*stored.*KeyId\((.*)\).*", check_error=False)
assert key_id2 == key_id
def test_one_account_send_bcc_setting(self, acfactory, lp):
ac1 = acfactory.get_online_configuring_account()
@@ -969,30 +938,6 @@ class TestOnlineAccount:
except queue.Empty:
pass # mark_seen_messages() has generated events before it returns
def test_reply_privately(self, acfactory):
ac1, ac2 = acfactory.get_two_online_accounts()
group1 = ac1.create_group_chat("group")
group1.add_contact(ac2)
group1.send_text("hello")
msg2 = ac2._evtracker.wait_next_messages_changed()
group2 = msg2.create_chat()
assert group2.get_name() == group1.get_name()
msg_reply = Message.new_empty(ac2, "text")
msg_reply.set_text("message reply")
msg_reply.quote = msg2
private_chat1 = ac1.create_chat(ac2)
private_chat2 = ac2.create_chat(ac1)
private_chat2.send_msg(msg_reply)
msg_reply1 = ac1._evtracker.wait_next_incoming_message()
assert msg_reply1.quoted_text == "hello"
assert not msg_reply1.chat.is_group()
assert msg_reply1.chat.id == private_chat1.id
def test_mdn_asymetric(self, acfactory, lp):
ac1, ac2 = acfactory.get_two_online_accounts(move=True)
@@ -1161,8 +1106,8 @@ class TestOnlineAccount:
# Majority prefers encryption now
assert msg5.is_encrypted()
def test_quote_encrypted(self, acfactory, lp):
"""Test that replies to encrypted messages with quotes are encrypted."""
def test_reply_encrypted(self, acfactory, lp):
"""Test that replies to encrypted messages are encrypted."""
ac1, ac2 = acfactory.get_two_online_accounts()
lp.sec("ac1: create chat with ac2")
@@ -1211,39 +1156,6 @@ class TestOnlineAccount:
assert msg_in.quoted_text == quoted_msg.text
assert msg_in.is_encrypted() == quoted_msg.is_encrypted()
def test_quote_attachment(self, tmpdir, acfactory, lp):
"""Test that replies with an attachment and a quote are received correctly."""
ac1, ac2 = acfactory.get_two_online_accounts()
lp.sec("ac1 creates chat with ac2")
chat1 = ac1.create_chat(ac2)
lp.sec("ac1 sends text message to ac2")
chat1.send_text("hi")
lp.sec("ac2 receives contact request from ac1")
received_message = ac2._evtracker.wait_next_messages_changed()
assert received_message.text == "hi"
basename = "attachment.txt"
p = os.path.join(tmpdir.strpath, basename)
with open(p, "w") as f:
f.write("data to send")
lp.sec("ac2 sends a reply to ac1")
chat2 = received_message.create_chat()
reply = Message.new_empty(ac2, "file")
reply.set_text("message reply")
reply.set_file(p)
reply.quote = received_message
chat2.send_msg(reply)
lp.sec("ac1 receives a reply from ac2")
received_reply = ac1._evtracker.wait_next_incoming_message()
assert received_reply.text == "message reply"
assert received_reply.quoted_text == received_message.text
assert open(received_reply.filename).read() == "data to send"
def test_saved_mime_on_received_message(self, acfactory, lp):
ac1, ac2 = acfactory.get_two_online_accounts()
@@ -1609,7 +1521,7 @@ class TestOnlineAccount:
lp.sec("ac1 blocks ac2")
contact = ac1.create_contact(ac2)
contact.block()
contact.set_blocked()
assert contact.is_blocked()
ev = ac1._evtracker.get_matching("DC_EVENT_CONTACTS_CHANGED")
assert ev.data1 == contact.id
@@ -2063,7 +1975,6 @@ class TestOnlineAccount:
assert ac1.direct_imap.idle_wait_for_seen()
ac1_clone = acfactory.clone_online_account(ac1)
ac1_clone.set_config("fetch_existing_msgs", "1")
ac1_clone._configtracker.wait_finish()
ac1_clone.start_io()

View File

@@ -63,14 +63,13 @@ def main():
ffi_toml = read_toml_version("deltachat-ffi/Cargo.toml")
assert core_toml == ffi_toml, (core_toml, ffi_toml)
if "alpha" not in newversion:
for line in open("CHANGELOG.md"):
## 1.25.0
if line.startswith("## "):
if line[2:].strip().startswith(newversion):
break
else:
raise SystemExit("CHANGELOG.md contains no entry for version: {}".format(newversion))
for line in open("CHANGELOG.md"):
## 1.25.0
if line.startswith("## "):
if line[2:].strip().startswith(newversion):
break
else:
raise SystemExit("CHANGELOG.md contains no entry for version: {}".format(newversion))
replace_toml_version("Cargo.toml", newversion)
replace_toml_version("deltachat-ffi/Cargo.toml", newversion)

View File

@@ -188,7 +188,7 @@ impl Accounts {
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, &file).await {
match crate::imex::imex(&ctx, crate::imex::ImexMode::ImportBackup, Some(file)).await {
Ok(_) => Ok(id),
Err(err) => {
// remove temp account
@@ -347,6 +347,7 @@ impl Config {
inner.accounts.push(AccountConfig {
id,
name: String::new(),
dir: target_dir.into(),
uuid,
});
@@ -413,6 +414,8 @@ impl Config {
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,

View File

@@ -63,12 +63,6 @@ impl<'a> BlobObject<'a> {
blobname: name.clone(),
cause: err.into(),
})?;
// workaround a bug in async-std
// (the executor does not handle blocking operation in Drop correctly,
// see https://github.com/async-rs/async-std/issues/900 )
let _ = file.flush().await;
let blob = BlobObject {
blobdir,
name: format!("$BLOBDIR/{}", name),
@@ -157,10 +151,6 @@ impl<'a> BlobObject<'a> {
cause: err,
});
}
// workaround, see create() for details
let _ = dst_file.flush().await;
let blob = BlobObject {
blobdir: context.get_blobdir(),
name: format!("$BLOBDIR/{}", name),

View File

@@ -337,7 +337,7 @@ impl ChatId {
);
/* Up to 2017-11-02 deleting a group also implied leaving it, see above why we have changed this. */
let chat = Chat::load_from_db(context, self).await?;
let _chat = Chat::load_from_db(context, self).await?;
context
.sql
.execute(
@@ -373,17 +373,6 @@ impl ChatId {
let j = job::Job::new(Action::Housekeeping, 0, Params::new(), 10);
job::add(context, j).await;
if chat.is_self_talk() {
let mut msg = Message::new(Viewtype::Text);
msg.text = Some(
context
.stock_str(StockMessage::SelfDeletedMsgBody)
.await
.into(),
);
add_device_msg(&context, None, Some(&mut msg)).await?;
}
Ok(())
}

View File

@@ -69,12 +69,8 @@ pub enum Config {
#[strum(props(default = "0"))] // also change MediaQuality.default() on changes
MediaQuality,
/// If set to "1", on the first time `start_io()` is called after configuring,
/// the newest existing messages are fetched.
/// Existing recipients are added to the contact database regardless of this setting.
#[strum(props(default = "0"))]
// disabled for now, we'll set this back to "1" at some point
FetchExistingMsgs,
#[strum(props(default = "1"))]
FetchExisting,
#[strum(props(default = "0"))]
KeyGenType,
@@ -253,16 +249,6 @@ impl Context {
job::schedule_resync(self).await;
ret
}
Config::InboxWatch => {
if self.get_config(Config::InboxWatch).await.as_deref() != value {
// If Inbox-watch is disabled and enabled again, do not fetch emails from in between.
// this avoids unexpected mass-downloads and -deletions (if delete_server_after is set)
if let Some(inbox) = self.get_config(Config::ConfiguredInboxFolder).await {
crate::imap::set_config_last_seen_uid(self, inbox, 0, 0).await;
}
}
self.sql.set_raw_config(self, key, value).await
}
_ => self.sql.set_raw_config(self, key, value).await,
}
}

View File

@@ -163,8 +163,8 @@ async fn configure(ctx: &Context, param: &mut LoginParam) -> Result<()> {
DC_LP_AUTH_NORMAL as i32
};
let ctx2 = ctx.clone();
let update_device_chats_handle = task::spawn(async move { ctx2.update_device_chats().await });
//let ctx2 = ctx.clone();
//let update_device_chats_handle = task::spawn(async move { ctx2.update_device_chats().await });
// Step 1: Load the parameters and check email-address and password
@@ -364,7 +364,7 @@ async fn configure(ctx: &Context, param: &mut LoginParam) -> Result<()> {
.await;
progress!(ctx, 940);
update_device_chats_handle.await?;
//update_device_chats_handle.await?;
Ok(())
}

View File

@@ -205,11 +205,6 @@ pub const WORSE_IMAGE_SIZE: u32 = 640;
// this value can be increased if the folder configuration is changed and must be redone on next program start
pub const DC_FOLDERS_CONFIGURED_VERSION: i32 = 3;
// if more recipients are needed in SMTP's `RCPT TO:` header, recipient-list is splitted to chunks.
// this does not affect MIME'e `To:` header.
// can be overwritten by the setting `max_smtp_rcpt_to` in provider-db.
pub const DEFAULT_MAX_SMTP_RCPT_TO: usize = 50;
#[derive(
Debug,
Display,

View File

@@ -163,6 +163,10 @@ impl Context {
/// Stops the IO scheduler.
pub async fn stop_io(&self) {
info!(self, "stopping IO");
if !self.is_io_running().await {
info!(self, "IO is not running");
return;
}
self.inner.stop_io().await;
}
@@ -479,19 +483,14 @@ impl InnerContext {
}
async fn stop_io(&self) {
if self.is_io_running().await {
let token = {
let lock = &*self.scheduler.read().await;
lock.pre_stop().await
};
{
let lock = &mut *self.scheduler.write().await;
lock.stop(token).await;
}
}
if let Some(ephemeral_task) = self.ephemeral_task.write().await.take() {
ephemeral_task.cancel().await;
assert!(self.is_io_running().await, "context is already stopped");
let token = {
let lock = &*self.scheduler.read().await;
lock.pre_stop().await
};
{
let lock = &mut *self.scheduler.write().await;
lock.stop(token).await;
}
}
}

View File

@@ -387,7 +387,6 @@ async fn add_parts(
// this message is a classic email not a chat-message nor a reply to one
match show_emails {
ShowEmails::Off => {
info!(context, "Classical email not shown (TRASH)");
*chat_id = ChatId::new(DC_CHAT_ID_TRASH);
allow_creation = false;
}
@@ -446,7 +445,10 @@ async fn add_parts(
// it might also be blocked and displayed in the deaddrop as a result
if chat_id.is_unset() && mime_parser.failure_report.is_some() {
*chat_id = ChatId::new(DC_CHAT_ID_TRASH);
info!(context, "Message belongs to an NDN (TRASH)",);
info!(
context,
"Message belongs to an NDN and is not shown in a chat.",
);
}
if chat_id.is_unset() {
@@ -487,7 +489,7 @@ async fn add_parts(
// check if the message belongs to a mailing list
if mime_parser.is_mailinglist_message() {
*chat_id = ChatId::new(DC_CHAT_ID_TRASH);
info!(context, "Message belongs to a mailing list (TRASH)");
info!(context, "Message belongs to a mailing list and is ignored.",);
}
}
@@ -531,7 +533,6 @@ async fn add_parts(
if chat_id.is_unset() {
// maybe from_id is null or sth. else is suspicious, move message to trash
*chat_id = ChatId::new(DC_CHAT_ID_TRASH);
info!(context, "No chat id for incoming msg (TRASH)")
}
// if the chat_id is blocked,
@@ -641,14 +642,13 @@ async fn add_parts(
}
if chat_id.is_unset() {
*chat_id = ChatId::new(DC_CHAT_ID_TRASH);
info!(context, "No chat id for outgoing message (TRASH)")
}
}
if fetching_existing_messages && mime_parser.decrypting_failed {
*chat_id = ChatId::new(DC_CHAT_ID_TRASH);
// We are only gathering old messages on first start. We do not want to add loads of non-decryptable messages to the chats.
info!(context, "Existing non-decipherable message. (TRASH)");
info!(context, "Dropping existing non-decipherable message.");
}
// Extract ephemeral timer from the message.
@@ -772,6 +772,9 @@ async fn add_parts(
*sent_timestamp = std::cmp::min(*sent_timestamp, rcvd_timestamp);
// unarchive chat
chat_id.unarchive(context).await?;
// if the mime-headers should be saved, find out its size
// (the mime-header ends with an empty line)
let save_mime_headers = context.get_config_bool(Config::SaveMimeHeaders).await;
@@ -893,10 +896,6 @@ async fn add_parts(
*insert_msg_id = *id;
}
if !is_hidden {
chat_id.unarchive(context).await?;
}
*hidden = is_hidden;
created_db_entries.extend(ids.iter().map(|id| (chat_id, *id)));
mime_parser.parts = new_parts;
@@ -1080,24 +1079,37 @@ async fn create_or_lookup_group(
set_better_msg(mime_parser, &better_msg);
}
let grpid = try_getting_grpid(mime_parser);
if grpid.is_empty() {
return create_or_lookup_adhoc_group(
context,
mime_parser,
allow_creation,
create_blocked,
from_id,
to_ids,
)
.await
.map_err(|err| {
info!(context, "could not create adhoc-group: {:?}", err);
err
});
let mut grpid = "".to_string();
if let Some(optional_field) = mime_parser.get(HeaderDef::ChatGroupId) {
grpid = optional_field.clone();
}
if grpid.is_empty() {
if let Some(extracted_grpid) = mime_parser
.get(HeaderDef::MessageId)
.and_then(|value| dc_extract_grpid_from_rfc724_mid(&value))
{
grpid = extracted_grpid.to_string();
} else if let Some(extracted_grpid) = extract_grpid(mime_parser, HeaderDef::InReplyTo) {
grpid = extracted_grpid.to_string();
} else if let Some(extracted_grpid) = extract_grpid(mime_parser, HeaderDef::References) {
grpid = extracted_grpid.to_string();
} else {
return create_or_lookup_adhoc_group(
context,
mime_parser,
allow_creation,
create_blocked,
from_id,
to_ids,
)
.await
.map_err(|err| {
info!(context, "could not create adhoc-group: {:?}", err);
err
});
}
}
// now we have a grpid that is non-empty
// but we might not know about this group
@@ -1267,7 +1279,6 @@ async fn create_or_lookup_group(
} else {
// The message was decrypted successfully, but contains a late "quit" or otherwise
// unwanted message.
info!(context, "message belongs to unwanted group (TRASH)");
return Ok((ChatId::new(DC_CHAT_ID_TRASH), chat_id_blocked));
}
}
@@ -1367,27 +1378,6 @@ async fn create_or_lookup_group(
Ok((chat_id, chat_id_blocked))
}
fn try_getting_grpid(mime_parser: &MimeMessage) -> String {
if let Some(optional_field) = mime_parser.get(HeaderDef::ChatGroupId) {
return optional_field.clone();
}
if let Some(extracted_grpid) = mime_parser
.get(HeaderDef::MessageId)
.and_then(|value| dc_extract_grpid_from_rfc724_mid(&value))
{
return extracted_grpid.to_string();
}
if !mime_parser.has_chat_version() {
if let Some(extracted_grpid) = extract_grpid(mime_parser, HeaderDef::InReplyTo) {
return extracted_grpid.to_string();
} else if let Some(extracted_grpid) = extract_grpid(mime_parser, HeaderDef::References) {
return extracted_grpid.to_string();
}
}
"".to_string()
}
/// try extract a grpid from a message-id list header value
fn extract_grpid(mime_parser: &MimeMessage, headerdef: HeaderDef) -> Option<&str> {
let header = mime_parser.get(headerdef)?;
@@ -2228,7 +2218,7 @@ mod tests {
let t = TestContext::new_alice().await;
// create one-to-one with bob, archive one-to-one
let bob_id = Contact::create(&t.ctx, "bob", "bob@example.com")
let bob_id = Contact::create(&t.ctx, "bob", "bob@exampel.org")
.await
.unwrap();
let one2one_id = chat::create_by_contact_id(&t.ctx, bob_id).await.unwrap();

View File

@@ -1174,31 +1174,22 @@ mod tests {
assert_eq!(msgs.len(), 1);
// do not repeat the warning every day ...
// (we test that for the 2 subsequent days, this may be the next month, so the result should be 1 or 2 device message)
maybe_warn_on_outdated(
&t.ctx,
timestamp_now + (365 + 1) * 24 * 60 * 60,
get_provider_update_timestamp(),
)
.await;
maybe_warn_on_outdated(
&t.ctx,
timestamp_now + (365 + 2) * 24 * 60 * 60,
get_provider_update_timestamp(),
)
.await;
let chats = Chatlist::try_load(&t.ctx, 0, None, None).await.unwrap();
assert_eq!(chats.len(), 1);
let device_chat_id = chats.get_chat_id(0);
let msgs = chat::get_chat_msgs(&t.ctx, device_chat_id, 0, None).await;
let test_len = msgs.len();
assert!(test_len == 1 || test_len == 2);
assert_eq!(msgs.len(), 1);
// ... but every month
// (forward generous 33 days to avoid being in the same month as in the previous check)
maybe_warn_on_outdated(
&t.ctx,
timestamp_now + (365 + 33) * 24 * 60 * 60,
timestamp_now + (365 + 31) * 24 * 60 * 60,
get_provider_update_timestamp(),
)
.await;
@@ -1206,6 +1197,6 @@ mod tests {
assert_eq!(chats.len(), 1);
let device_chat_id = chats.get_chat_id(0);
let msgs = chat::get_chat_msgs(&t.ctx, device_chat_id, 0, None).await;
assert_eq!(msgs.len(), test_len + 1);
assert_eq!(msgs.len(), 2);
}
}

View File

@@ -457,6 +457,24 @@ impl Imap {
Ok(())
}
async fn get_config_last_seen_uid<S: AsRef<str>>(
&self,
context: &Context,
folder: S,
) -> (u32, u32) {
let key = format!("imap.mailbox.{}", folder.as_ref());
if let Some(entry) = context.sql.get_raw_config(context, &key).await {
// the entry has the format `imap.mailbox.<folder>=<uidvalidity>:<lastseenuid>`
let mut parts = entry.split(':');
(
parts.next().unwrap_or_default().parse().unwrap_or(0),
parts.next().unwrap_or_default().parse().unwrap_or(0),
)
} else {
(0, 0)
}
}
/// Synchronizes UIDs in the database with UIDs on the server.
///
/// It is assumed that no operations are taking place on the same
@@ -541,7 +559,7 @@ impl Imap {
self.select_folder(context, Some(folder)).await?;
// compare last seen UIDVALIDITY against the current one
let (uid_validity, last_seen_uid) = get_config_last_seen_uid(context, &folder).await;
let (uid_validity, last_seen_uid) = self.get_config_last_seen_uid(context, &folder).await;
let config = &mut self.config;
let mailbox = config
@@ -567,7 +585,8 @@ impl Imap {
// id we do not do this here, we'll miss the first message
// as we will get in here again and fetch from lastseenuid+1 then
set_config_last_seen_uid(context, &folder, new_uid_validity, 0).await;
self.set_config_last_seen_uid(context, &folder, new_uid_validity, 0)
.await;
return Ok((new_uid_validity, 0));
}
@@ -611,7 +630,8 @@ impl Imap {
}
};
set_config_last_seen_uid(context, &folder, new_uid_validity, new_last_seen_uid).await;
self.set_config_last_seen_uid(context, &folder, new_uid_validity, new_last_seen_uid)
.await;
if uid_validity != 0 || last_seen_uid != 0 {
job::schedule_resync(context).await;
}
@@ -694,7 +714,8 @@ impl Imap {
let last_one = new_last_seen_uid.max(new_last_seen_uid_processed);
if last_one > last_seen_uid {
set_config_last_seen_uid(context, &folder, uid_validity, last_one).await;
self.set_config_last_seen_uid(context, &folder, uid_validity, last_one)
.await;
}
if read_errors == 0 {
@@ -840,6 +861,23 @@ impl Imap {
Ok(msgs)
}
async fn set_config_last_seen_uid<S: AsRef<str>>(
&self,
context: &Context,
folder: S,
uidvalidity: u32,
lastseenuid: u32,
) {
let key = format!("imap.mailbox.{}", folder.as_ref());
let val = format!("{}:{}", uidvalidity, lastseenuid);
context
.sql
.set_raw_config(context, &key, Some(&val))
.await
.ok();
}
/// Fetches a list of messages by server UID.
/// The passed in list of uids must be sorted.
///
@@ -1673,36 +1711,6 @@ fn get_fallback_folder(delimiter: &str) -> String {
format!("INBOX{}DeltaChat", delimiter)
}
pub async fn set_config_last_seen_uid<S: AsRef<str>>(
context: &Context,
folder: S,
uidvalidity: u32,
lastseenuid: u32,
) {
let key = format!("imap.mailbox.{}", folder.as_ref());
let val = format!("{}:{}", uidvalidity, lastseenuid);
context
.sql
.set_raw_config(context, &key, Some(&val))
.await
.ok();
}
async fn get_config_last_seen_uid<S: AsRef<str>>(context: &Context, folder: S) -> (u32, u32) {
let key = format!("imap.mailbox.{}", folder.as_ref());
if let Some(entry) = context.sql.get_raw_config(context, &key).await {
// the entry has the format `imap.mailbox.<folder>=<uidvalidity>:<lastseenuid>`
let mut parts = entry.split(':');
(
parts.next().unwrap_or_default().parse().unwrap_or(0),
parts.next().unwrap_or_default().parse().unwrap_or(0),
)
} else {
(0, 0)
}
}
#[cfg(test)]
mod tests {
use super::*;

View File

@@ -31,7 +31,6 @@ use crate::param::*;
use crate::pgp;
use crate::sql::{self, Sql};
use crate::stock::StockMessage;
use ::pgp::types::KeyTrait;
use async_tar::Archive;
// Name of the database file in the backup.
@@ -79,7 +78,11 @@ pub enum ImexMode {
///
/// Only one import-/export-progress can run at the same time.
/// To cancel an import-/export-progress, drop the future returned by this function.
pub async fn imex(context: &Context, what: ImexMode, param1: impl AsRef<Path>) -> Result<()> {
pub async fn imex(
context: &Context,
what: ImexMode,
param1: Option<impl AsRef<Path>>,
) -> Result<()> {
let cancel = context.alloc_ongoing().await?;
let res = async {
@@ -410,8 +413,6 @@ async fn set_self_key(
},
)
.await?;
info!(context, "stored self key: {:?}", keypair.secret.key_id());
Ok(())
}
@@ -438,11 +439,19 @@ pub fn normalize_setup_code(s: &str) -> String {
out
}
async fn imex_inner(context: &Context, what: ImexMode, path: impl AsRef<Path>) -> Result<()> {
info!(context, "Import/export dir: {}", path.as_ref().display());
ensure!(context.sql.is_open().await, "Database not opened.");
async fn imex_inner(
context: &Context,
what: ImexMode,
param: Option<impl AsRef<Path>>,
) -> Result<()> {
ensure!(param.is_some(), "No Import/export dir/file given.");
info!(context, "Import/export process started.");
context.emit_event(EventType::ImexProgress(10));
ensure!(context.sql.is_open().await, "Database not opened.");
let path = param.ok_or_else(|| format_err!("Imex: Param was None"))?;
if what == ImexMode::ExportBackup || what == ImexMode::ExportSelfKeys {
// before we export anything, make sure the private key exists
if e2ee::ensure_secret_key_exists(context).await.is_err() {
@@ -866,12 +875,6 @@ async fn import_self_keys(context: &Context, dir: impl AsRef<Path>) -> Result<()
continue;
}
}
info!(
context,
"considering key file: {}",
path_plus_name.display()
);
match dc_read_file(context, &path_plus_name).await {
Ok(buf) => {
let armored = std::string::String::from_utf8_lossy(&buf);
@@ -961,7 +964,7 @@ where
let any_key = key as &dyn Any;
let kind = if any_key.downcast_ref::<SignedPublicKey>().is_some() {
"public"
} else if any_key.downcast_ref::<SignedSecretKey>().is_some() {
} else if any_key.downcast_ref::<SignedPublicKey>().is_some() {
"private"
} else {
"unknown"
@@ -969,12 +972,7 @@ where
let id = id.map_or("default".into(), |i| i.to_string());
dir.as_ref().join(format!("{}-key-{}.asc", kind, &id))
};
info!(
context,
"Exporting key {:?} to {}",
key.key_id(),
file_name.display()
);
info!(context, "Exporting key {}", file_name.display());
dc_delete_file(context, &file_name).await;
let content = key.to_asc(None).into_bytes();
@@ -1042,7 +1040,7 @@ mod tests {
}
#[async_std::test]
async fn test_export_public_key_to_asc_file() {
async fn test_export_key_to_asc_file() {
let context = TestContext::new().await;
let key = alice_keypair().public;
let blobdir = "$BLOBDIR";
@@ -1056,35 +1054,19 @@ mod tests {
assert_eq!(bytes, key.to_asc(None).into_bytes());
}
#[async_std::test]
async fn test_export_private_key_to_asc_file() {
let context = TestContext::new().await;
let key = alice_keypair().secret;
let blobdir = "$BLOBDIR";
assert!(export_key_to_asc_file(&context.ctx, blobdir, None, &key)
.await
.is_ok());
let blobdir = context.ctx.get_blobdir().to_str().unwrap();
let filename = format!("{}/private-key-default.asc", blobdir);
let bytes = async_std::fs::read(&filename).await.unwrap();
assert_eq!(bytes, key.to_asc(None).into_bytes());
}
#[async_std::test]
async fn test_export_and_import_key() {
let context = TestContext::new().await;
context.configure_alice().await;
let blobdir = context.ctx.get_blobdir().to_str().unwrap();
if let Err(err) = imex(&context.ctx, ImexMode::ExportSelfKeys, blobdir).await {
panic!("got error on export: {:?}", err);
}
let blobdir = "$BLOBDIR";
assert!(imex(&context.ctx, ImexMode::ExportSelfKeys, Some(blobdir))
.await
.is_ok());
let context2 = TestContext::new().await;
context2.configure_alice().await;
if let Err(err) = imex(&context2.ctx, ImexMode::ImportSelfKeys, blobdir).await {
panic!("got error on import: {:?}", err);
}
let blobdir = context.ctx.get_blobdir().to_str().unwrap();
assert!(imex(&context.ctx, ImexMode::ImportSelfKeys, Some(blobdir))
.await
.is_ok());
}
#[test]

View File

@@ -639,7 +639,7 @@ impl Job {
add_all_recipients_as_contacts(context, imap, Config::ConfiguredMvboxFolder).await;
add_all_recipients_as_contacts(context, imap, Config::ConfiguredInboxFolder).await;
if context.get_config_bool(Config::FetchExistingMsgs).await {
if context.get_config_bool(Config::FetchExisting).await {
for config in &[
Config::ConfiguredMvboxFolder,
Config::ConfiguredInboxFolder,

View File

@@ -1,9 +1,5 @@
use chrono::TimeZone;
use lettre_email::{mime, Address, Header, MimeMultipartType, PartBuilder};
use std::collections::HashSet;
use vcard::properties::Photo;
use vcard::values::image_value::ImageValue;
use vcard::{Set, VCard};
use crate::blob::BlobObject;
use crate::chat::{self, Chat};
@@ -995,24 +991,13 @@ impl<'a, 'b> MimeFactory<'a, 'b> {
if self.attach_selfavatar {
match context.get_config(Config::Selfavatar).await {
Some(path) => {
match build_selfavatar_file(context, &path) {
Ok((part, filename)) => {
parts.push(part);
protected_headers.push(Header::new("Chat-User-Avatar".into(), filename))
}
Err(err) => {
warn!(context, "mimefactory: cannot attach selfavatar: {}", err)
}
};
match build_vcard_part(context, &path).await {
Ok(part) => {
parts.push(part);
}
Err(err) => warn!(context, "mimefactory: cannot build vCard: {}", err),
};
}
Some(path) => match build_selfavatar_file(context, &path) {
Ok((part, filename)) => {
parts.push(part);
protected_headers.push(Header::new("Chat-User-Avatar".into(), filename))
}
Err(err) => warn!(context, "mimefactory: cannot attach selfavatar: {}", err),
},
None => protected_headers.push(Header::new("Chat-User-Avatar".into(), "0".into())),
}
}
@@ -1156,23 +1141,13 @@ async fn build_body_file(
Viewtype::Image | Viewtype::Gif => format!(
"{}.{}",
if base_name.is_empty() {
chrono::Utc
.timestamp(msg.timestamp_sort as i64, 0)
.format("image_%Y-%m-%d_%H-%M-%S")
.to_string()
"image"
} else {
base_name.to_string()
base_name
},
&suffix,
),
Viewtype::Video => format!(
"video_{}.{}",
chrono::Utc
.timestamp(msg.timestamp_sort as i64, 0)
.format("%Y-%m-%d_%H-%M-%S")
.to_string(),
&suffix
),
Viewtype::Video => format!("video.{}", &suffix),
_ => blob.as_file_name().to_string(),
};
@@ -1214,40 +1189,6 @@ async fn build_body_file(
Ok((mail, filename_to_send))
}
async fn build_vcard_file(context: &Context, avatar_path: &str) -> Result<String, Error> {
let avatar_blob = BlobObject::from_path(context, avatar_path)?;
let displayname = context
.get_config(Config::Displayname)
.await
.unwrap_or_default();
let mut vcard = VCard::from_formatted_name_str(&displayname)?;
// TODO: add KIND:individual
let mut photos = HashSet::new();
if let Ok(image_value) = ImageValue::from_file(avatar_blob.to_abs_path()) {
let photo = Photo::from_image_value(image_value);
photos.insert(photo);
}
vcard.photos = Set::from_hash_set(photos).ok();
Ok(vcard.to_string())
}
async fn build_vcard_part(context: &Context, avatar_path: &str) -> Result<PartBuilder, Error> {
let body = build_vcard_file(context, avatar_path).await?;
let encoded_body = wrapped_base64_encode(&body.as_bytes());
let part = PartBuilder::new()
.content_type(&mime::TEXT_VCARD)
.header((
"Content-Disposition",
"attachment; filename=\"{avatar.vcf}\"",
))
.header(("Content-Transfer-Encoding", "base64"))
.body(encoded_body);
Ok(part)
}
fn build_selfavatar_file(context: &Context, path: &str) -> Result<(PartBuilder, String), Error> {
let blob = BlobObject::from_path(context, path)?;
let filename_to_send = match blob.suffix() {

View File

@@ -295,8 +295,7 @@ impl MimeMessage {
/// Squashes mutlipart chat messages with attachment into single-part messages.
///
/// Delta Chat sends attachments, such as images, in two-part messages, with the first message
/// containing a description. If such a message is detected, text from the first part can be
/// moved to the second part, and the first part dropped.
/// containing an explanation. If such a message is detected, first part can be safely dropped.
#[allow(clippy::indexing_slicing)]
fn squash_attachment_parts(&mut self) {
if let [textpart, filepart] = &self.parts[..] {
@@ -316,9 +315,6 @@ impl MimeMessage {
// insert new one
filepart.msg = self.parts[0].msg.clone();
if let Some(quote) = self.parts[0].param.get(Param::Quote) {
filepart.param.set(Param::Quote, quote);
}
// forget the one we use now
self.parts[0].msg = "".to_string();
@@ -814,26 +810,6 @@ impl MimeMessage {
return;
}
}
if mime_type.type_() == "text/x-vcard"
|| mime_type.type_() == "text/vcard"
|| filename.ends_with(".vcf")
|| filename.ends_with(".vcard")
{
println!("Parsing vcard {:?}", String::from_utf8_lossy(decoded_data));
for contact in ical::VcardParser::new(decoded_data) {
println!("Parsing contact {:?}", contact);
if let Ok(contact) = contact {
for property in contact.properties {
println!("Parsed property {:?}", property);
if property.name == "email" {
}
}
}
}
return;
}
/* we have a regular file attachment,
write decoded data to new blob object */
@@ -1652,22 +1628,6 @@ mod tests {
assert_eq!(mimeparser.group_avatar, None);
}
#[async_std::test]
async fn test_mimeparser_with_vcard() {
let t = TestContext::new().await;
let raw = include_bytes!("../test-data/message/vcard.eml");
let mimeparser = MimeMessage::from_bytes(&t.ctx, &raw[..]).await.unwrap();
assert_eq!(mimeparser.parts.len(), 1);
assert_eq!(mimeparser.parts[0].typ, Viewtype::Text);
assert_eq!(
mimeparser.parts[0].msg,
"vCard example An example of vCard sent from Thunderbird."
);
assert_eq!(mimeparser.user_avatar, None);
assert_eq!(mimeparser.group_avatar, None);
}
#[async_std::test]
async fn test_mimeparser_message_kml() {
let context = TestContext::new().await;
@@ -2308,53 +2268,4 @@ From: alice <alice@example.org>
);
assert_eq!(message.parts[0].msg, "");
}
#[async_std::test]
async fn parse_quote_top_posting() {
let context = TestContext::new().await;
let raw = br##"Content-Type: text/plain; charset=utf-8; format=flowed; delsp=no
Subject: Re: top posting
MIME-Version: 1.0
In-Reply-To: <bar@example.org>
Message-ID: <foo@example.org>
To: bob <bob@example.org>
From: alice <alice@example.org>
A reply.
On 2020-10-25, Bob wrote:
> A quote.
"##;
let message = MimeMessage::from_bytes(&context.ctx, &raw[..])
.await
.unwrap();
assert_eq!(message.get_subject(), Some("Re: top posting".to_string()));
assert_eq!(message.parts.len(), 1);
assert_eq!(message.parts[0].typ, Viewtype::Text);
assert_eq!(
message.parts[0].param.get(Param::Quote).unwrap(),
"A quote."
);
assert_eq!(message.parts[0].msg, "A reply.");
}
#[async_std::test]
async fn test_attachment_quote() {
let context = TestContext::new().await;
let raw = include_bytes!("../test-data/message/quote_attach.eml");
let mimeparser = MimeMessage::from_bytes(&context.ctx, &raw[..])
.await
.unwrap();
assert_eq!(mimeparser.get_subject().unwrap(), "Message from Alice");
assert_eq!(mimeparser.parts.len(), 1);
assert_eq!(mimeparser.parts[0].msg, "Reply");
assert_eq!(
mimeparser.parts[0].param.get(Param::Quote).unwrap(),
"Quote"
);
assert_eq!(mimeparser.parts[0].typ, Viewtype::File);
}
}

View File

@@ -32,7 +32,6 @@ static P_AKTIVIX_ORG: Lazy<Provider> = Lazy::new(|| Provider {
],
config_defaults: None,
strict_tls: false,
max_smtp_rcpt_to: None,
oauth2_authorizer: None,
});
@@ -47,7 +46,6 @@ static P_AOL: Lazy<Provider> = Lazy::new(|| {
],
config_defaults: None,
strict_tls: false,
max_smtp_rcpt_to: None,
oauth2_authorizer: None,
}
});
@@ -76,7 +74,6 @@ static P_ARCOR_DE: Lazy<Provider> = Lazy::new(|| Provider {
],
config_defaults: None,
strict_tls: false,
max_smtp_rcpt_to: None,
oauth2_authorizer: None,
});
@@ -104,7 +101,6 @@ static P_AUTISTICI_ORG: Lazy<Provider> = Lazy::new(|| Provider {
],
config_defaults: None,
strict_tls: false,
max_smtp_rcpt_to: None,
oauth2_authorizer: None,
});
@@ -132,7 +128,6 @@ static P_BLUEWIN_CH: Lazy<Provider> = Lazy::new(|| Provider {
],
config_defaults: None,
strict_tls: false,
max_smtp_rcpt_to: None,
oauth2_authorizer: None,
});
@@ -146,21 +141,20 @@ static P_BUZON_UY: Lazy<Provider> = Lazy::new(|| Provider {
Server {
protocol: IMAP,
socket: STARTTLS,
hostname: "mail.buzon.uy",
hostname: "buzon.uy",
port: 143,
username_pattern: EMAIL,
},
Server {
protocol: SMTP,
socket: STARTTLS,
hostname: "mail.buzon.uy",
hostname: "buzon.uy",
port: 587,
username_pattern: EMAIL,
},
],
config_defaults: None,
strict_tls: true,
max_smtp_rcpt_to: None,
oauth2_authorizer: None,
});
@@ -188,7 +182,6 @@ static P_CHELLO_AT: Lazy<Provider> = Lazy::new(|| Provider {
],
config_defaults: None,
strict_tls: false,
max_smtp_rcpt_to: None,
oauth2_authorizer: None,
});
@@ -201,7 +194,6 @@ static P_COMCAST: Lazy<Provider> = Lazy::new(|| Provider {
server: vec![],
config_defaults: None,
strict_tls: false,
max_smtp_rcpt_to: None,
oauth2_authorizer: None,
});
@@ -214,7 +206,6 @@ static P_DISMAIL_DE: Lazy<Provider> = Lazy::new(|| Provider {
server: vec![],
config_defaults: None,
strict_tls: false,
max_smtp_rcpt_to: None,
oauth2_authorizer: None,
});
@@ -227,7 +218,6 @@ static P_DISROOT: Lazy<Provider> = Lazy::new(|| Provider {
server: vec![],
config_defaults: None,
strict_tls: true,
max_smtp_rcpt_to: None,
oauth2_authorizer: None,
});
@@ -279,7 +269,6 @@ static P_DUBBY_ORG: Lazy<Provider> = Lazy::new(|| Provider {
},
]),
strict_tls: true,
max_smtp_rcpt_to: None,
oauth2_authorizer: None,
});
@@ -296,7 +285,6 @@ static P_EXAMPLE_COM: Lazy<Provider> = Lazy::new(|| {
],
config_defaults: None,
strict_tls: false,
max_smtp_rcpt_to: None,
oauth2_authorizer: None,
}
});
@@ -311,7 +299,6 @@ static P_FASTMAIL: Lazy<Provider> = Lazy::new(|| Provider {
server: vec![],
config_defaults: None,
strict_tls: false,
max_smtp_rcpt_to: None,
oauth2_authorizer: None,
});
@@ -326,7 +313,6 @@ static P_FIREMAIL_DE: Lazy<Provider> = Lazy::new(|| {
],
config_defaults: None,
strict_tls: false,
max_smtp_rcpt_to: None,
oauth2_authorizer: None,
}
});
@@ -357,7 +343,6 @@ static P_FIVE_CHAT: Lazy<Provider> = Lazy::new(|| Provider {
},
]),
strict_tls: true,
max_smtp_rcpt_to: None,
oauth2_authorizer: None,
});
@@ -385,7 +370,6 @@ static P_FREENET_DE: Lazy<Provider> = Lazy::new(|| Provider {
],
config_defaults: None,
strict_tls: false,
max_smtp_rcpt_to: None,
oauth2_authorizer: None,
});
@@ -402,7 +386,6 @@ static P_GMAIL: Lazy<Provider> = Lazy::new(|| {
],
config_defaults: None,
strict_tls: true,
max_smtp_rcpt_to: None,
oauth2_authorizer: Some(Oauth2Authorizer::Gmail),
}
});
@@ -438,7 +421,6 @@ static P_GMX_NET: Lazy<Provider> = Lazy::new(|| Provider {
],
config_defaults: None,
strict_tls: false,
max_smtp_rcpt_to: None,
oauth2_authorizer: None,
});
@@ -468,7 +450,6 @@ static P_HERMES_RADIO: Lazy<Provider> = Lazy::new(|| Provider {
},
]),
strict_tls: false,
max_smtp_rcpt_to: None,
oauth2_authorizer: None,
});
@@ -483,7 +464,6 @@ static P_HEY_COM: Lazy<Provider> = Lazy::new(|| {
],
config_defaults: None,
strict_tls: false,
max_smtp_rcpt_to: None,
oauth2_authorizer: None,
}
});
@@ -497,7 +477,6 @@ static P_I_UA: Lazy<Provider> = Lazy::new(|| Provider {
server: vec![],
config_defaults: None,
strict_tls: false,
max_smtp_rcpt_to: None,
oauth2_authorizer: None,
});
@@ -526,7 +505,6 @@ static P_ICLOUD: Lazy<Provider> = Lazy::new(|| Provider {
],
config_defaults: None,
strict_tls: false,
max_smtp_rcpt_to: None,
oauth2_authorizer: None,
});
@@ -539,7 +517,6 @@ static P_KOLST_COM: Lazy<Provider> = Lazy::new(|| Provider {
server: vec![],
config_defaults: None,
strict_tls: false,
max_smtp_rcpt_to: None,
oauth2_authorizer: None,
});
@@ -552,7 +529,6 @@ static P_KONTENT_COM: Lazy<Provider> = Lazy::new(|| Provider {
server: vec![],
config_defaults: None,
strict_tls: false,
max_smtp_rcpt_to: None,
oauth2_authorizer: None,
});
@@ -565,7 +541,6 @@ static P_MAIL_RU: Lazy<Provider> = Lazy::new(|| Provider {
server: vec![],
config_defaults: None,
strict_tls: false,
max_smtp_rcpt_to: None,
oauth2_authorizer: None,
});
@@ -578,7 +553,6 @@ static P_MAILBOX_ORG: Lazy<Provider> = Lazy::new(|| Provider {
server: vec![],
config_defaults: None,
strict_tls: true,
max_smtp_rcpt_to: None,
oauth2_authorizer: None,
});
@@ -601,10 +575,9 @@ static P_NAUTA_CU: Lazy<Provider> = Lazy::new(|| {
ConfigDefault { key: Config::MvboxMove, value: "0" },
ConfigDefault { key: Config::E2eeEnabled, value: "0" },
ConfigDefault { key: Config::MediaQuality, value: "1" },
ConfigDefault { key: Config::FetchExistingMsgs, value: "0" },
ConfigDefault { key: Config::FetchExisting, value: "0" },
]),
strict_tls: false,
max_smtp_rcpt_to: Some(20),
oauth2_authorizer: None,
}
});
@@ -622,7 +595,6 @@ static P_OUTLOOK_COM: Lazy<Provider> = Lazy::new(|| {
],
config_defaults: None,
strict_tls: false,
max_smtp_rcpt_to: None,
oauth2_authorizer: None,
}
});
@@ -651,7 +623,6 @@ static P_POSTEO: Lazy<Provider> = Lazy::new(|| Provider {
],
config_defaults: None,
strict_tls: true,
max_smtp_rcpt_to: None,
oauth2_authorizer: None,
});
@@ -666,7 +637,6 @@ static P_PROTONMAIL: Lazy<Provider> = Lazy::new(|| {
],
config_defaults: None,
strict_tls: false,
max_smtp_rcpt_to: None,
oauth2_authorizer: None,
}
});
@@ -680,7 +650,6 @@ static P_RISEUP_NET: Lazy<Provider> = Lazy::new(|| Provider {
server: vec![],
config_defaults: None,
strict_tls: true,
max_smtp_rcpt_to: None,
oauth2_authorizer: None,
});
@@ -693,7 +662,6 @@ static P_ROGERS_COM: Lazy<Provider> = Lazy::new(|| Provider {
server: vec![],
config_defaults: None,
strict_tls: false,
max_smtp_rcpt_to: None,
oauth2_authorizer: None,
});
@@ -706,7 +674,6 @@ static P_SYSTEMLI_ORG: Lazy<Provider> = Lazy::new(|| Provider {
server: vec![],
config_defaults: None,
strict_tls: true,
max_smtp_rcpt_to: None,
oauth2_authorizer: None,
});
@@ -721,7 +688,6 @@ static P_T_ONLINE: Lazy<Provider> = Lazy::new(|| {
],
config_defaults: None,
strict_tls: false,
max_smtp_rcpt_to: None,
oauth2_authorizer: None,
}
});
@@ -774,7 +740,6 @@ static P_TESTRUN: Lazy<Provider> = Lazy::new(|| Provider {
},
]),
strict_tls: true,
max_smtp_rcpt_to: None,
oauth2_authorizer: None,
});
@@ -802,7 +767,6 @@ static P_TISCALI_IT: Lazy<Provider> = Lazy::new(|| Provider {
],
config_defaults: None,
strict_tls: false,
max_smtp_rcpt_to: None,
oauth2_authorizer: None,
});
@@ -815,7 +779,6 @@ static P_UKR_NET: Lazy<Provider> = Lazy::new(|| Provider {
server: vec![],
config_defaults: None,
strict_tls: false,
max_smtp_rcpt_to: None,
oauth2_authorizer: None,
});
@@ -843,7 +806,6 @@ static P_UNDERNET_UY: Lazy<Provider> = Lazy::new(|| Provider {
],
config_defaults: None,
strict_tls: true,
max_smtp_rcpt_to: None,
oauth2_authorizer: None,
});
@@ -856,7 +818,6 @@ static P_VFEMAIL: Lazy<Provider> = Lazy::new(|| Provider {
server: vec![],
config_defaults: None,
strict_tls: false,
max_smtp_rcpt_to: None,
oauth2_authorizer: None,
});
@@ -884,7 +845,6 @@ static P_VODAFONE_DE: Lazy<Provider> = Lazy::new(|| Provider {
],
config_defaults: None,
strict_tls: false,
max_smtp_rcpt_to: None,
oauth2_authorizer: None,
});
@@ -902,7 +862,6 @@ static P_WEB_DE: Lazy<Provider> = Lazy::new(|| {
],
config_defaults: None,
strict_tls: false,
max_smtp_rcpt_to: None,
oauth2_authorizer: None,
}
});
@@ -920,7 +879,6 @@ static P_YAHOO: Lazy<Provider> = Lazy::new(|| {
],
config_defaults: None,
strict_tls: false,
max_smtp_rcpt_to: None,
oauth2_authorizer: None,
}
});
@@ -949,7 +907,6 @@ static P_YANDEX_RU: Lazy<Provider> = Lazy::new(|| Provider {
],
config_defaults: None,
strict_tls: true,
max_smtp_rcpt_to: None,
oauth2_authorizer: Some(Oauth2Authorizer::Yandex),
});
@@ -977,7 +934,6 @@ static P_ZIGGO_NL: Lazy<Provider> = Lazy::new(|| Provider {
],
config_defaults: None,
strict_tls: false,
max_smtp_rcpt_to: None,
oauth2_authorizer: None,
});
@@ -1153,4 +1109,4 @@ pub static PROVIDER_DATA: Lazy<HashMap<&'static str, &'static Provider>> = Lazy:
});
pub static PROVIDER_UPDATED: Lazy<chrono::NaiveDate> =
Lazy::new(|| chrono::NaiveDate::from_ymd(2020, 10, 30));
Lazy::new(|| chrono::NaiveDate::from_ymd(2020, 10, 17));

View File

@@ -75,7 +75,6 @@ pub struct Provider {
pub server: Vec<Server>,
pub config_defaults: Option<Vec<ConfigDefault>>,
pub strict_tls: bool,
pub max_smtp_rcpt_to: Option<u16>,
pub oauth2_authorizer: Option<Oauth2Authorizer>,
}

View File

@@ -104,9 +104,6 @@ def process_data(data, file):
strict_tls = data.get("strict_tls", False)
strict_tls = "true" if strict_tls else "false"
max_smtp_rcpt_to = data.get("max_smtp_rcpt_to", 0)
max_smtp_rcpt_to = "Some(" + str(max_smtp_rcpt_to) + ")" if max_smtp_rcpt_to != 0 else "None"
oauth2 = data.get("oauth2", "")
oauth2 = "Some(Oauth2Authorizer::" + camel(oauth2) + ")" if oauth2 != "" else "None"
@@ -122,7 +119,6 @@ def process_data(data, file):
provider += " server: vec![\n" + server + " ],\n"
provider += " config_defaults: " + config_defaults + ",\n"
provider += " strict_tls: " + strict_tls + ",\n"
provider += " max_smtp_rcpt_to: " + max_smtp_rcpt_to + ",\n"
provider += " oauth2_authorizer: " + oauth2 + ",\n"
provider += "});\n\n"
else:

View File

@@ -69,11 +69,9 @@ async fn inbox_loop(ctx: Context, started: Sender<()>, inbox_handlers: ImapConne
}
Some(job) => {
// Let the fetch run, but return back to the job afterwards.
info!(ctx, "postponing imap-job {} to run fetch...", job);
jobs_loaded = 0;
if ctx.get_config_bool(Config::InboxWatch).await {
info!(ctx, "postponing imap-job {} to run fetch...", job);
fetch(&ctx, &mut connection).await;
}
fetch(&ctx, &mut connection).await;
}
None => {
jobs_loaded = 0;

View File

@@ -71,7 +71,7 @@ pub fn simplify(mut input: String, is_chat_message: bool) -> (String, bool, Opti
let lines = split_lines(&input);
let (lines, is_forwarded) = skip_forward_header(&lines);
let (lines, mut top_quote) = remove_top_quote(lines);
let (lines, top_quote) = remove_top_quote(lines);
let original_lines = &lines;
let lines = remove_message_footer(lines);
@@ -79,16 +79,12 @@ pub fn simplify(mut input: String, is_chat_message: bool) -> (String, bool, Opti
render_message(lines, false)
} else {
let (lines, has_nonstandard_footer) = remove_nonstandard_footer(lines);
let (lines, mut bottom_quote) = remove_bottom_quote(lines);
if top_quote.is_none() && bottom_quote.is_some() {
std::mem::swap(&mut top_quote, &mut bottom_quote);
}
let (lines, has_bottom_quote) = remove_bottom_quote(lines);
if lines.iter().all(|it| it.trim().is_empty()) {
render_message(original_lines, false)
} else {
render_message(lines, has_nonstandard_footer || bottom_quote.is_some())
render_message(lines, has_nonstandard_footer || has_bottom_quote)
}
};
(text, is_forwarded, top_quote)
@@ -109,27 +105,16 @@ fn skip_forward_header<'a>(lines: &'a [&str]) -> (&'a [&'a str], bool) {
}
#[allow(clippy::indexing_slicing)]
fn remove_bottom_quote<'a>(lines: &'a [&str]) -> (&'a [&'a str], Option<String>) {
let mut first_quoted_line = lines.len();
fn remove_bottom_quote<'a>(lines: &'a [&str]) -> (&'a [&'a str], bool) {
let mut last_quoted_line = None;
for (l, line) in lines.iter().enumerate().rev() {
if is_plain_quote(line) {
if last_quoted_line.is_none() {
first_quoted_line = l + 1;
}
last_quoted_line = Some(l)
} else if !is_empty_line(line) {
break;
}
}
if let Some(mut l_last) = last_quoted_line {
let quoted_text = lines[l_last..first_quoted_line]
.iter()
.map(|s| {
s.strip_prefix(">")
.map_or(*s, |u| u.strip_prefix(" ").unwrap_or(u))
})
.join("\n");
if l_last > 1 && is_empty_line(lines[l_last - 1]) {
l_last -= 1
}
@@ -139,9 +124,9 @@ fn remove_bottom_quote<'a>(lines: &'a [&str]) -> (&'a [&'a str], Option<String>)
l_last -= 1
}
}
(&lines[..l_last], Some(quoted_text))
(&lines[..l_last], true)
} else {
(lines, None)
(lines, false)
}
}

View File

@@ -192,8 +192,7 @@ impl Smtp {
};
let security = match lp.security {
Socket::Plain => smtp::ClientSecurity::None,
Socket::STARTTLS => smtp::ClientSecurity::Required(tls_parameters),
Socket::STARTTLS | Socket::Plain => smtp::ClientSecurity::Opportunistic(tls_parameters),
_ => smtp::ClientSecurity::Wrapper(tls_parameters),
};

View File

@@ -3,11 +3,8 @@
use super::Smtp;
use async_smtp::*;
use crate::config::Config;
use crate::constants::DEFAULT_MAX_SMTP_RCPT_TO;
use crate::context::Context;
use crate::events::EventType;
use crate::provider::get_provider_info;
use itertools::Itertools;
use std::time::Duration;
@@ -37,51 +34,37 @@ impl Smtp {
) -> Result<()> {
let message_len_bytes = message.len();
let mut chunk_size = DEFAULT_MAX_SMTP_RCPT_TO;
if let Some(provider) = get_provider_info(
&context
.get_config(Config::ConfiguredAddr)
let recipients_display = recipients.iter().map(|x| x.to_string()).join(",");
let envelope =
Envelope::new(self.from.clone(), recipients).map_err(Error::EnvelopeError)?;
let mail = SendableEmail::new(
envelope,
format!("{}", job_id), // only used for internal logging
message,
);
if let Some(ref mut transport) = self.transport {
// The timeout is 1min + 3min per MB.
let timeout = 60 + (180 * message_len_bytes / 1_000_000) as u64;
transport
.send_with_timeout(mail, Some(&Duration::from_secs(timeout)))
.await
.unwrap_or_default(),
) {
if let Some(max_smtp_rcpt_to) = provider.max_smtp_rcpt_to {
chunk_size = max_smtp_rcpt_to as usize;
}
}
.map_err(Error::SendError)?;
for recipients_chunk in recipients.chunks(chunk_size).into_iter() {
let recipients = recipients_chunk.to_vec();
let recipients_display = recipients.iter().map(|x| x.to_string()).join(",");
context.emit_event(EventType::SmtpMessageSent(format!(
"Message len={} was smtp-sent to {}",
message_len_bytes, recipients_display
)));
self.last_success = Some(std::time::SystemTime::now());
let envelope =
Envelope::new(self.from.clone(), recipients).map_err(Error::EnvelopeError)?;
let mail = SendableEmail::new(
envelope,
format!("{}", job_id), // only used for internal logging
&message,
Ok(())
} else {
warn!(
context,
"uh? SMTP has no transport, failed to send to {}", recipients_display
);
if let Some(ref mut transport) = self.transport {
// The timeout is 1min + 3min per MB.
let timeout = 60 + (180 * message_len_bytes / 1_000_000) as u64;
transport
.send_with_timeout(mail, Some(&Duration::from_secs(timeout)))
.await
.map_err(Error::SendError)?;
context.emit_event(EventType::SmtpMessageSent(format!(
"Message len={} was smtp-sent to {}",
message_len_bytes, recipients_display
)));
self.last_success = Some(std::time::SystemTime::now());
} else {
warn!(
context,
"uh? SMTP has no transport, failed to send to {}", recipients_display
);
return Err(Error::NoTransport);
}
Err(Error::NoTransport)
}
Ok(())
}
}

View File

@@ -244,10 +244,6 @@ pub enum StockMessage {
// used in summaries, a noun, not a verb (not: "to reply")
#[strum(props(fallback = "Reply"))]
ReplyNoun = 90,
#[strum(props(fallback = "You deleted the \"Saved messages\" chat.\n\n\
To use the \"Saved messages\" feature again, create a new chat with yourself."))]
SelfDeletedMsgBody = 91,
}
/*
@@ -467,7 +463,6 @@ mod tests {
use crate::constants::DC_CONTACT_ID_SELF;
use crate::chat::Chat;
use crate::chatlist::Chatlist;
use num_traits::ToPrimitive;
@@ -659,31 +654,8 @@ mod tests {
let chats = Chatlist::try_load(&t.ctx, 0, None, None).await.unwrap();
assert_eq!(chats.len(), 2);
let chat0 = Chat::load_from_db(&t.ctx, chats.get_chat_id(0))
.await
.unwrap();
let (self_talk_id, device_chat_id) = if chat0.is_self_talk() {
(chats.get_chat_id(0), chats.get_chat_id(1))
} else {
(chats.get_chat_id(1), chats.get_chat_id(0))
};
// delete self-talk first; this adds a message to device-chat about how self-talk can be restored
let device_chat_msgs_before = chat::get_chat_msgs(&t.ctx, device_chat_id, 0, None)
.await
.len();
self_talk_id.delete(&t.ctx).await.ok();
assert_eq!(
chat::get_chat_msgs(&t.ctx, device_chat_id, 0, None)
.await
.len(),
device_chat_msgs_before + 1
);
// delete device chat
device_chat_id.delete(&t.ctx).await.ok();
// check, that the chatlist is empty
chats.get_chat_id(0).delete(&t.ctx).await.ok();
chats.get_chat_id(1).delete(&t.ctx).await.ok();
let chats = Chatlist::try_load(&t.ctx, 0, None, None).await.unwrap();
assert_eq!(chats.len(), 0);

View File

@@ -1,27 +0,0 @@
Subject: Message from Alice
MIME-Version: 1.0
In-Reply-To: <Mr.7h_HyOuM3Dz.xcp4v8QiQua@testrun.org>
Date: Sun, 08 Nov 2020 01:16:26 +0000
Chat-Version: 1.0
Message-ID: <Mr.126R7OsBKvk.dGGBC5WcsLF@testrun.org>
To: Bob <bob@example.org>
From: Alice <alice@testrun.org>
Content-Type: multipart/mixed; boundary="uWbWY2IyEtJ8wZmp282Na11hxBBXlV"
--uWbWY2IyEtJ8wZmp282Na11hxBBXlV
Content-Type: text/plain; charset=utf-8; format=flowed; delsp=no
> Quote
Reply
--uWbWY2IyEtJ8wZmp282Na11hxBBXlV
Content-Type: text/plain
Content-Disposition: attachment; filename="attachment.txt"
Content-Transfer-Encoding: base64
ZGF0YSB0byBzZW5k
--uWbWY2IyEtJ8wZmp282Na11hxBBXlV--

View File

@@ -1,38 +0,0 @@
Return-Path: <alice@example.org>
Delivered-To: bob@example.org
To: bob@example.org
From: Alice <alice@example.org>
Subject: vCard example
Message-ID: <foobar@example.org>
Date: Sun, 22 Nov 2020 22:44:00 +0000
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:78.0) Gecko/20100101
Thunderbird/78.4.3
MIME-Version: 1.0
Content-Type: multipart/mixed;
boundary="------------01973B3475205164D5F9F507"
Content-Language: en-US
This is a multi-part message in MIME format.
--------------01973B3475205164D5F9F507
Content-Type: text/plain; charset=utf-8
Content-Transfer-Encoding: 7bit
An example of vCard sent from Thunderbird.
--------------01973B3475205164D5F9F507
Content-Type: text/x-vcard; charset=utf-8;
name="alice.vcf"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
filename="alice.vcf"
begin:vcard
fn:Alice Foobar
n:Foobar;Alice
email;internet:alice@example.org
x-mozilla-html:FALSE
version:2.1
end:vcard
--------------01973B3475205164D5F9F507--