mirror of
https://github.com/chatmail/core.git
synced 2026-06-25 09:06:37 +03:00
Compare commits
2 Commits
vcard
...
search-bug
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ea9252925c | ||
|
|
7e3029aa9c |
61
CHANGELOG.md
61
CHANGELOG.md
@@ -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
216
Cargo.lock
generated
@@ -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"
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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") */
|
||||
|
||||
/*
|
||||
* @}
|
||||
|
||||
@@ -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, ¶m1)
|
||||
.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
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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] = [
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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,
|
||||
|
||||
10
src/blob.rs
10
src/blob.rs
@@ -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),
|
||||
|
||||
13
src/chat.rs
13
src/chat.rs
@@ -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(())
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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::*;
|
||||
|
||||
72
src/imex.rs
72
src/imex.rs
@@ -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]
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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>,
|
||||
}
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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),
|
||||
};
|
||||
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
}
|
||||
|
||||
32
src/stock.rs
32
src/stock.rs
@@ -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);
|
||||
|
||||
|
||||
@@ -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--
|
||||
|
||||
@@ -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--
|
||||
Reference in New Issue
Block a user