From e021a59b8789e7cccb2035f90f08411b38b3945e Mon Sep 17 00:00:00 2001 From: link2xt Date: Wed, 15 Feb 2023 16:27:34 +0000 Subject: [PATCH 01/13] python: do not inherit from `object` --- python/pyproject.toml | 2 +- python/src/deltachat/account.py | 2 +- python/src/deltachat/chat.py | 2 +- python/src/deltachat/contact.py | 2 +- python/src/deltachat/message.py | 2 +- python/src/deltachat/provider.py | 2 +- python/src/deltachat/reactions.py | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/python/pyproject.toml b/python/pyproject.toml index 569e849db..580b02535 100644 --- a/python/pyproject.toml +++ b/python/pyproject.toml @@ -45,7 +45,7 @@ git_describe_command = "git describe --dirty --tags --long --match py-*.*" line-length = 120 [tool.ruff] -select = ["E", "F", "W", "YTT", "C4", "ISC", "ICN", "TID", "DTZ", "PLC", "PLE", "PLW", "PIE", "COM", "UP032"] +select = ["E", "F", "W", "YTT", "C4", "ISC", "ICN", "TID", "DTZ", "PLC", "PLE", "PLW", "PIE", "COM", "UP004", "UP032"] line-length = 120 [tool.isort] diff --git a/python/src/deltachat/account.py b/python/src/deltachat/account.py index e9ec5e311..a1c5b3e52 100644 --- a/python/src/deltachat/account.py +++ b/python/src/deltachat/account.py @@ -53,7 +53,7 @@ def get_dc_info_as_dict(dc_context): return info_dict -class Account(object): +class Account: """Each account is tied to a sqlite database file which is fully managed by the underlying deltachat core library. All public Account methods are meant to be memory-safe and return memory-safe objects. diff --git a/python/src/deltachat/chat.py b/python/src/deltachat/chat.py index 1548829ae..77fac073d 100644 --- a/python/src/deltachat/chat.py +++ b/python/src/deltachat/chat.py @@ -18,7 +18,7 @@ from .cutil import ( from .message import Message -class Chat(object): +class Chat: """Chat object which manages members and through which you can send and retrieve messages. You obtain instances of it through :class:`deltachat.account.Account`. diff --git a/python/src/deltachat/contact.py b/python/src/deltachat/contact.py index fc38cdd73..f317eb315 100644 --- a/python/src/deltachat/contact.py +++ b/python/src/deltachat/contact.py @@ -9,7 +9,7 @@ from .chat import Chat from .cutil import from_dc_charpointer, from_optional_dc_charpointer -class Contact(object): +class Contact: """Delta-Chat Contact. You obtain instances of it through :class:`deltachat.account.Account`. diff --git a/python/src/deltachat/message.py b/python/src/deltachat/message.py index d84713bf8..f0a1cd08b 100644 --- a/python/src/deltachat/message.py +++ b/python/src/deltachat/message.py @@ -12,7 +12,7 @@ from .cutil import as_dc_charpointer, from_dc_charpointer, from_optional_dc_char from .reactions import Reactions -class Message(object): +class Message: """Message object. You obtain instances of it through :class:`deltachat.account.Account` or diff --git a/python/src/deltachat/provider.py b/python/src/deltachat/provider.py index d760dbabb..29313a0fb 100644 --- a/python/src/deltachat/provider.py +++ b/python/src/deltachat/provider.py @@ -8,7 +8,7 @@ class ProviderNotFoundError(Exception): """The provider information was not found.""" -class Provider(object): +class Provider: """ Provider information. diff --git a/python/src/deltachat/reactions.py b/python/src/deltachat/reactions.py index 968bcb0d5..1ab2744d0 100644 --- a/python/src/deltachat/reactions.py +++ b/python/src/deltachat/reactions.py @@ -4,7 +4,7 @@ from .capi import ffi, lib from .cutil import from_dc_charpointer, iter_array -class Reactions(object): +class Reactions: """Reactions object. You obtain instances of it through :class:`deltachat.message.Message`. From 01653a881a50e7d26d1e12aca7a865d704f8dfa6 Mon Sep 17 00:00:00 2001 From: link2xt Date: Wed, 15 Feb 2023 16:28:16 +0000 Subject: [PATCH 02/13] python: do not import print function --- python/pyproject.toml | 2 +- python/src/deltachat/account.py | 1 - python/src/deltachat/testplugin.py | 1 - 3 files changed, 1 insertion(+), 3 deletions(-) diff --git a/python/pyproject.toml b/python/pyproject.toml index 580b02535..56c6d87f4 100644 --- a/python/pyproject.toml +++ b/python/pyproject.toml @@ -45,7 +45,7 @@ git_describe_command = "git describe --dirty --tags --long --match py-*.*" line-length = 120 [tool.ruff] -select = ["E", "F", "W", "YTT", "C4", "ISC", "ICN", "TID", "DTZ", "PLC", "PLE", "PLW", "PIE", "COM", "UP004", "UP032"] +select = ["E", "F", "W", "YTT", "C4", "ISC", "ICN", "TID", "DTZ", "PLC", "PLE", "PLW", "PIE", "COM", "UP004", "UP010", "UP032"] line-length = 120 [tool.isort] diff --git a/python/src/deltachat/account.py b/python/src/deltachat/account.py index a1c5b3e52..295e9d02f 100644 --- a/python/src/deltachat/account.py +++ b/python/src/deltachat/account.py @@ -1,6 +1,5 @@ """Account class implementation.""" -from __future__ import print_function import os from array import array diff --git a/python/src/deltachat/testplugin.py b/python/src/deltachat/testplugin.py index fe1d85436..9e8d7b536 100644 --- a/python/src/deltachat/testplugin.py +++ b/python/src/deltachat/testplugin.py @@ -1,4 +1,3 @@ -from __future__ import print_function import fnmatch import io From b3ecbbc8b3029c2c1fa033f0b66cd9a916028ab4 Mon Sep 17 00:00:00 2001 From: link2xt Date: Wed, 15 Feb 2023 16:32:19 +0000 Subject: [PATCH 03/13] python: do not import print function in tests --- python/tests/test_2_increation.py | 1 - python/tests/test_3_offline.py | 1 - 2 files changed, 2 deletions(-) diff --git a/python/tests/test_2_increation.py b/python/tests/test_2_increation.py index 33c264ef1..7906b35c9 100644 --- a/python/tests/test_2_increation.py +++ b/python/tests/test_2_increation.py @@ -1,4 +1,3 @@ -from __future__ import print_function import os.path import shutil diff --git a/python/tests/test_3_offline.py b/python/tests/test_3_offline.py index 8a01eb2a2..bb3c3c12e 100644 --- a/python/tests/test_3_offline.py +++ b/python/tests/test_3_offline.py @@ -1,4 +1,3 @@ -from __future__ import print_function import os import time From 4f68e94fb3df2f6cefbee944125eb80830c6ca16 Mon Sep 17 00:00:00 2001 From: link2xt Date: Wed, 15 Feb 2023 16:33:05 +0000 Subject: [PATCH 04/13] python: use f-strings instead of percent-encoding --- python/pyproject.toml | 2 +- python/src/deltachat/account.py | 2 +- python/tests/stress_test_db.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/python/pyproject.toml b/python/pyproject.toml index 56c6d87f4..e1ed72bea 100644 --- a/python/pyproject.toml +++ b/python/pyproject.toml @@ -45,7 +45,7 @@ git_describe_command = "git describe --dirty --tags --long --match py-*.*" line-length = 120 [tool.ruff] -select = ["E", "F", "W", "YTT", "C4", "ISC", "ICN", "TID", "DTZ", "PLC", "PLE", "PLW", "PIE", "COM", "UP004", "UP010", "UP032"] +select = ["E", "F", "W", "YTT", "C4", "ISC", "ICN", "TID", "DTZ", "PLC", "PLE", "PLW", "PIE", "COM", "UP004", "UP010", "UP031", "UP032"] line-length = 120 [tool.isort] diff --git a/python/src/deltachat/account.py b/python/src/deltachat/account.py index 295e9d02f..61d74d793 100644 --- a/python/src/deltachat/account.py +++ b/python/src/deltachat/account.py @@ -301,7 +301,7 @@ class Account: elif isinstance(obj, str): displayname, addr = parseaddr(obj) else: - raise TypeError("don't know how to create chat for %r" % (obj,)) + raise TypeError(f"don't know how to create chat for {obj!r}") if name is None and displayname: name = displayname diff --git a/python/tests/stress_test_db.py b/python/tests/stress_test_db.py index f46bcdf65..a8b942a09 100644 --- a/python/tests/stress_test_db.py +++ b/python/tests/stress_test_db.py @@ -14,7 +14,7 @@ def test_db_busy_error(acfactory, tmpdir): def log(string): with log_lock: - print("%3.2f %s" % (time.time() - starttime, string)) + print(f"{time.time() - starttime:3.2f} {string}") # make a number of accounts accounts = acfactory.get_many_online_accounts(3) From cd3f1fe8748a841d53f9a00bcb882040318030c9 Mon Sep 17 00:00:00 2001 From: link2xt Date: Wed, 15 Feb 2023 17:15:43 +0000 Subject: [PATCH 05/13] python: autoformat with `black` --- python/src/deltachat/testplugin.py | 1 - python/tests/test_2_increation.py | 1 - python/tests/test_3_offline.py | 1 - 3 files changed, 3 deletions(-) diff --git a/python/src/deltachat/testplugin.py b/python/src/deltachat/testplugin.py index 9e8d7b536..d9d3e4ebb 100644 --- a/python/src/deltachat/testplugin.py +++ b/python/src/deltachat/testplugin.py @@ -1,4 +1,3 @@ - import fnmatch import io import os diff --git a/python/tests/test_2_increation.py b/python/tests/test_2_increation.py index 7906b35c9..c7e35119a 100644 --- a/python/tests/test_2_increation.py +++ b/python/tests/test_2_increation.py @@ -1,4 +1,3 @@ - import os.path import shutil from filecmp import cmp diff --git a/python/tests/test_3_offline.py b/python/tests/test_3_offline.py index bb3c3c12e..e7a1a6231 100644 --- a/python/tests/test_3_offline.py +++ b/python/tests/test_3_offline.py @@ -1,4 +1,3 @@ - import os import time from datetime import datetime, timedelta, timezone From 0de5125de8a49b2f4a457269d592aba3470878da Mon Sep 17 00:00:00 2001 From: Simon Laux Date: Wed, 15 Feb 2023 19:46:34 +0100 Subject: [PATCH 06/13] jsonrpc: `get_messages` now returns a map with `MessageLoadResult` instead of failing completely if one of the requested messages could not be loaded. (#4038) * jsonrpc: `get_messages` now returns a map with `MessageLoadResult` instead of failing completely if one of the requested messages could not be loaded. * add pr number to changelog * format errors with causes instead of debug output also for chatlistitemfetchresult --- CHANGELOG.md | 2 ++ deltachat-jsonrpc/src/api/mod.rs | 19 +++++++++++++++---- deltachat-jsonrpc/src/api/types/message.rs | 7 +++++++ .../typescript/example/example.ts | 4 +++- 4 files changed, 27 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 21bafce20..a0a2169ce 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,9 @@ deltachat-rpc-server now reads the next request as soon as previous request handler is spawned. ### API-Changes + - Remove `MimeMessage::from_bytes()` public interface. #4033 +- BREAKING Types: jsonrpc: `get_messages` now returns a map with `MessageLoadResult` instead of failing completely if one of the requested messages could not be loaded. #4038 ## 1.108.0 diff --git a/deltachat-jsonrpc/src/api/mod.rs b/deltachat-jsonrpc/src/api/mod.rs index 49ed900c3..22f261ced 100644 --- a/deltachat-jsonrpc/src/api/mod.rs +++ b/deltachat-jsonrpc/src/api/mod.rs @@ -45,6 +45,7 @@ use types::message::MessageObject; use types::provider_info::ProviderInfo; use types::webxdc::WebxdcMessageInfo; +use self::types::message::MessageLoadResult; use self::types::{ chat::{BasicChat, JSONRPCChatVisibility, MuteDuration}, location::JsonrpcLocation, @@ -465,7 +466,7 @@ impl CommandApi { Ok(res) => res, Err(err) => ChatListItemFetchResult::Error { id: entry.0, - error: format!("{err:?}"), + error: format!("{err:#}"), }, }, ); @@ -945,17 +946,27 @@ impl CommandApi { MsgId::new(message_id).get_html(&ctx).await } + /// get multiple messages in one call, + /// if loading one message fails the error is stored in the result object in it's place. + /// + /// this is the batch variant of [get_message] async fn get_messages( &self, account_id: u32, message_ids: Vec, - ) -> Result> { + ) -> Result> { let ctx = self.get_context(account_id).await?; - let mut messages: HashMap = HashMap::new(); + let mut messages: HashMap = HashMap::new(); for message_id in message_ids { + let message_result = MessageObject::from_message_id(&ctx, message_id).await; messages.insert( message_id, - MessageObject::from_message_id(&ctx, message_id).await?, + match message_result { + Ok(message) => MessageLoadResult::Message(message), + Err(error) => MessageLoadResult::LoadingError { + error: format!("{error:#}"), + }, + }, ); } Ok(messages) diff --git a/deltachat-jsonrpc/src/api/types/message.rs b/deltachat-jsonrpc/src/api/types/message.rs index 9030467f6..04a850715 100644 --- a/deltachat-jsonrpc/src/api/types/message.rs +++ b/deltachat-jsonrpc/src/api/types/message.rs @@ -19,6 +19,13 @@ use super::contact::ContactObject; use super::reactions::JSONRPCReactions; use super::webxdc::WebxdcMessageInfo; +#[derive(Serialize, TypeDef)] +#[serde(rename_all = "camelCase", tag = "variant")] +pub enum MessageLoadResult { + Message(MessageObject), + LoadingError { error: String }, +} + #[derive(Serialize, TypeDef)] #[serde(rename = "Message", rename_all = "camelCase")] pub struct MessageObject { diff --git a/deltachat-jsonrpc/typescript/example/example.ts b/deltachat-jsonrpc/typescript/example/example.ts index c180a9e31..0caa2de06 100644 --- a/deltachat-jsonrpc/typescript/example/example.ts +++ b/deltachat-jsonrpc/typescript/example/example.ts @@ -81,7 +81,9 @@ async function run() { messageIds ); for (const [_messageId, message] of Object.entries(messages)) { - write($main, `

${message.text}

`); + if (message.variant === "message") + write($main, `

${message.text}

`); + else write($main, `

loading error: ${message.error}

`); } } } From 032da4fb1a50261ffb5960bacd479f13e1762d41 Mon Sep 17 00:00:00 2001 From: link2xt Date: Wed, 15 Feb 2023 16:03:23 +0000 Subject: [PATCH 07/13] python: add py.typed file It marks the package as supporting typing according to PEP 561 Also remove zip-safe option from setuptools configuration for deltachat-rpc-client. It is deprecated in setuptools and not used for wheel package format according to --- CHANGELOG.md | 1 + deltachat-rpc-client/pyproject.toml | 7 ------- python/pyproject.toml | 5 +++++ python/src/deltachat/py.typed | 0 4 files changed, 6 insertions(+), 7 deletions(-) create mode 100644 python/src/deltachat/py.typed diff --git a/CHANGELOG.md b/CHANGELOG.md index a0a2169ce..3cfd88c21 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ ### Changes - deltachat-rpc-client: use `dataclass` for `Account`, `Chat`, `Contact` and `Message` #4042 +- python: mark bindings as supporting typing according to PEP 561 #4045 ### Fixes - deltachat-rpc-server: do not block stdin while processing the request. #4041 diff --git a/deltachat-rpc-client/pyproject.toml b/deltachat-rpc-client/pyproject.toml index 7797ac903..ef76fa82b 100644 --- a/deltachat-rpc-client/pyproject.toml +++ b/deltachat-rpc-client/pyproject.toml @@ -13,13 +13,6 @@ dynamic = [ "version" ] -[tool.setuptools] -# We declare the package not-zip-safe so that our type hints are also available -# when checking client code that uses our (installed) package. -# Ref: -# https://mypy.readthedocs.io/en/stable/installed_packages.html?highlight=zip#using-installed-packages-with-mypy-pep-561 -zip-safe = false - [tool.setuptools.package-data] deltachat_rpc_client = [ "py.typed" diff --git a/python/pyproject.toml b/python/pyproject.toml index e1ed72bea..4aa6666db 100644 --- a/python/pyproject.toml +++ b/python/pyproject.toml @@ -36,6 +36,11 @@ dynamic = [ [project.entry-points.pytest11] "deltachat.testplugin" = "deltachat.testplugin" +[tool.setuptools.package-data] +deltachat = [ + "py.typed" +] + [tool.setuptools_scm] root = ".." tag_regex = '^(?Ppy-)?(?P[^\+]+)(?P.*)?$' diff --git a/python/src/deltachat/py.typed b/python/src/deltachat/py.typed new file mode 100644 index 000000000..e69de29bb From 893794f4e790cbf78c2d9daa731ea6a1e5777ea7 Mon Sep 17 00:00:00 2001 From: link2xt Date: Wed, 15 Feb 2023 15:04:11 +0000 Subject: [PATCH 08/13] accounts: retry filesystem operations in migrate_account() --- CHANGELOG.md | 1 + src/accounts.rs | 61 ++++++++++++++++++++++++++++--------------------- 2 files changed, 36 insertions(+), 26 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3cfd88c21..49d5e3c35 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ ### Changes - deltachat-rpc-client: use `dataclass` for `Account`, `Chat`, `Contact` and `Message` #4042 - python: mark bindings as supporting typing according to PEP 561 #4045 +- retry filesystem operations during account migration #4043 ### Fixes - deltachat-rpc-server: do not block stdin while processing the request. #4041 diff --git a/src/accounts.rs b/src/accounts.rs index 5ea6965e0..91c0e1a5f 100644 --- a/src/accounts.rs +++ b/src/accounts.rs @@ -1,6 +1,7 @@ //! # Account manager module. use std::collections::BTreeMap; +use std::future::Future; use std::path::{Path, PathBuf}; use anyhow::{ensure, Context as _, Result}; @@ -150,27 +151,9 @@ impl Accounts { if let Some(cfg) = self.config.get_account(id) { let account_path = self.dir.join(cfg.dir); - // Spend up to 1 minute trying to remove the files. - // Files may remain locked up to 30 seconds due to r2d2 bug: - // https://github.com/sfackler/r2d2/issues/99 - let mut counter = 0; - loop { - counter += 1; - - if let Err(err) = fs::remove_dir_all(&account_path) - .await - .context("failed to remove account data") - { - if counter > 60 { - return Err(err); - } - - // Wait 1 second and try again. - tokio::time::sleep(std::time::Duration::from_millis(1000)).await; - } else { - break; - } - } + try_many_times(|| fs::remove_dir_all(&account_path)) + .await + .context("failed to remove account data")?; } self.config.remove_account(id).await?; @@ -202,10 +185,10 @@ impl Accounts { fs::create_dir_all(self.dir.join(&account_config.dir)) .await .context("failed to create dir")?; - fs::rename(&dbfile, &new_dbfile) + try_many_times(|| fs::rename(&dbfile, &new_dbfile)) .await .context("failed to rename dbfile")?; - fs::rename(&blobdir, &new_blobdir) + try_many_times(|| fs::rename(&blobdir, &new_blobdir)) .await .context("failed to rename blobdir")?; if walfile.exists() { @@ -229,11 +212,10 @@ impl Accounts { Ok(account_config.id) } Err(err) => { - // remove temp account - fs::remove_dir_all(std::path::PathBuf::from(&account_config.dir)) + let account_path = std::path::PathBuf::from(&account_config.dir); + try_many_times(|| fs::remove_dir_all(&account_path)) .await .context("failed to remove account data")?; - self.config.remove_account(account_config.id).await?; // set selection back @@ -488,6 +470,33 @@ impl Config { } } +/// Spend up to 1 minute trying to do the operation. +/// +/// Files may remain locked up to 30 seconds due to r2d2 bug: +/// +async fn try_many_times(f: F) -> std::result::Result<(), T> +where + F: Fn() -> Fut, + Fut: Future>, +{ + let mut counter = 0; + loop { + counter += 1; + + if let Err(err) = f().await { + if counter > 60 { + return Err(err); + } + + // Wait 1 second and try again. + tokio::time::sleep(std::time::Duration::from_millis(1000)).await; + } else { + break; + } + } + Ok(()) +} + /// Configuration of a single account. #[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] struct AccountConfig { From c52b48b0f5105c6036480248e9e833b37f17fa87 Mon Sep 17 00:00:00 2001 From: link2xt Date: Sun, 17 Apr 2022 22:14:32 +0000 Subject: [PATCH 09/13] sql: update version first in migration transactions In execute_migration transaction first update the version, and only then execute the rest of the migration. This ensures that transaction is immediately recognized as write transaction and there is no need to promote it from read transaction to write transaction later, e.g. in the case of "DROP TABLE IF EXISTS" that is a read only operation if the table does not exist. Promoting a read transaction to write transaction may result in an error if database is locked. --- src/sql/migrations.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sql/migrations.rs b/src/sql/migrations.rs index 92f1c9551..02f13ecf3 100644 --- a/src/sql/migrations.rs +++ b/src/sql/migrations.rs @@ -723,14 +723,14 @@ impl Sql { async fn execute_migration(&self, query: &'static str, version: i32) -> Result<()> { self.transaction(move |transaction| { - transaction.execute_batch(query)?; - // set raw config inside the transaction transaction.execute( "UPDATE config SET value=? WHERE keyname=?;", paramsv![format!("{version}"), VERSION_CFG], )?; + transaction.execute_batch(query)?; + Ok(()) }) .await From 7d2cca8633dc37f479900e96b03b108b8c143957 Mon Sep 17 00:00:00 2001 From: link2xt Date: Sun, 17 Apr 2022 22:16:16 +0000 Subject: [PATCH 10/13] sql: enable auto_vacuum on all connections --- CHANGELOG.md | 1 + src/sql.rs | 31 +++++++++++-------------------- 2 files changed, 12 insertions(+), 20 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 49d5e3c35..f3781b5d3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ ### Fixes - deltachat-rpc-server: do not block stdin while processing the request. #4041 deltachat-rpc-server now reads the next request as soon as previous request handler is spawned. +- enable `auto_vacuum` on all SQL connections #2955 ### API-Changes diff --git a/src/sql.rs b/src/sql.rs index c01322c8b..4fcb90ee9 100644 --- a/src/sql.rs +++ b/src/sql.rs @@ -213,6 +213,17 @@ impl Sql { Duration::from_secs(10).as_millis() ))?; c.pragma_update(None, "key", passphrase.clone())?; + // Try to enable auto_vacuum. This will only be + // applied if the database is new or after successful + // VACUUM, which usually happens before backup export. + // When auto_vacuum is INCREMENTAL, it is possible to + // use PRAGMA incremental_vacuum to return unused + // database pages to the filesystem. + c.pragma_update(None, "auto_vacuum", "INCREMENTAL".to_string())?; + + c.pragma_update(None, "journal_mode", "WAL".to_string())?; + // Default synchronous=FULL is much slower. NORMAL is sufficient for WAL mode. + c.pragma_update(None, "synchronous", "NORMAL".to_string())?; Ok(()) }); @@ -228,26 +239,6 @@ impl Sql { async fn try_open(&self, context: &Context, dbfile: &Path, passphrase: String) -> Result<()> { *self.pool.write().await = Some(Self::new_pool(dbfile, passphrase.to_string())?); - { - let conn = self.get_conn().await?; - tokio::task::block_in_place(move || -> Result<()> { - // Try to enable auto_vacuum. This will only be - // applied if the database is new or after successful - // VACUUM, which usually happens before backup export. - // When auto_vacuum is INCREMENTAL, it is possible to - // use PRAGMA incremental_vacuum to return unused - // database pages to the filesystem. - conn.pragma_update(None, "auto_vacuum", "INCREMENTAL".to_string())?; - - // journal_mode is persisted, it is sufficient to change it only for one handle. - conn.pragma_update(None, "journal_mode", "WAL".to_string())?; - - // Default synchronous=FULL is much slower. NORMAL is sufficient for WAL mode. - conn.pragma_update(None, "synchronous", "NORMAL".to_string())?; - Ok(()) - })?; - } - self.run_migrations(context).await?; Ok(()) From ae19c9b3310158bc5604815c79f789afb7ea6448 Mon Sep 17 00:00:00 2001 From: link2xt Date: Mon, 13 Feb 2023 17:09:58 +0000 Subject: [PATCH 11/13] Add more documentation --- deltachat-ffi/deltachat.h | 2 +- src/accounts.rs | 2 + src/chat.rs | 9 +-- src/config.rs | 20 ++++++- src/constants.rs | 16 +++++ src/events.rs | 61 ++++++++++++++----- src/headerdef.rs | 23 +++++++- src/lib.rs | 4 +- src/mimefactory.rs | 2 + src/mimeparser.rs | 12 +++- src/oauth2.rs | 12 ++-- src/pgp.rs | 5 +- src/qr.rs | 119 +++++++++++++++++++++++++++++++++++++- src/qr/dclogin_scheme.rs | 31 ++++++++++ src/qr_code_generator.rs | 6 +- src/receive_imf.rs | 7 ++- src/sql.rs | 19 +++++- src/stock_str.rs | 5 +- src/tools.rs | 5 ++ src/webxdc.rs | 4 +- 20 files changed, 317 insertions(+), 47 deletions(-) diff --git a/deltachat-ffi/deltachat.h b/deltachat-ffi/deltachat.h index e457ecf11..a24b500df 100644 --- a/deltachat-ffi/deltachat.h +++ b/deltachat-ffi/deltachat.h @@ -5671,7 +5671,7 @@ void dc_event_unref(dc_event_t* event); #define DC_EVENT_INCOMING_MSG 2005 /** - * Downloading a bunch of messages just finished. This is an experimental + * Downloading a bunch of messages just finished. This is an * event to allow the UI to only show one notification per message bunch, * instead of cluttering the user with many notifications. * For each of the msg_ids, an additional #DC_EVENT_INCOMING_MSG event was emitted before. diff --git a/src/accounts.rs b/src/accounts.rs index 91c0e1a5f..e01cc49c2 100644 --- a/src/accounts.rs +++ b/src/accounts.rs @@ -161,6 +161,8 @@ impl Accounts { } /// Migrate an existing account into this structure. + /// + /// Returns the ID of new account. pub async fn migrate_account(&mut self, dbfile: PathBuf) -> Result { let blobdir = Context::derive_blobdir(&dbfile); let walfile = Context::derive_walfile(&dbfile); diff --git a/src/chat.rs b/src/chat.rs index 0017eeabc..cc217af15 100644 --- a/src/chat.rs +++ b/src/chat.rs @@ -2075,8 +2075,7 @@ async fn prepare_msg_blob(context: &Context, msg: &mut Message) -> Result<()> { Ok(()) } -/// Prepares a message to be send out -/// - Checks if chat can be sent to +/// Prepares a message to be sent out. async fn prepare_msg_common( context: &Context, chat_id: ChatId, @@ -2084,6 +2083,8 @@ async fn prepare_msg_common( change_state_to: MessageState, ) -> Result { let mut chat = Chat::load_from_db(context, chat_id).await?; + + // Check if the chat can be sent to. if let Some(reason) = chat.why_cant_send(context).await? { bail!("cannot send to {}: {}", chat_id, reason); } @@ -2141,7 +2142,7 @@ pub async fn is_contact_in_chat( Ok(exists) } -/// Send a message defined by a dc_msg_t object to a chat. +/// Sends a message object to a chat. /// /// Sends the event #DC_EVENT_MSGS_CHANGED on succcess. /// However, this does not imply, the message really reached the recipient - @@ -3294,7 +3295,7 @@ pub async fn set_chat_name(context: &Context, chat_id: ChatId, new_name: &str) - Ok(()) } -/// Set a new profile image for the chat. +/// Sets a new profile image for the chat. /// /// The profile image can only be set when you are a member of the /// chat. To remove the profile image pass an empty string for the diff --git a/src/config.rs b/src/config.rs index 91cc37e94..67d77811f 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,7 +1,5 @@ //! # Key-value configuration management. -#![allow(missing_docs)] - use anyhow::{ensure, Context as _, Result}; use strum::{EnumProperty, IntoEnumIterator}; use strum_macros::{AsRefStr, Display, EnumIter, EnumProperty, EnumString}; @@ -195,6 +193,8 @@ pub enum Config { /// Configured IMAP server security (e.g. TLS, STARTTLS). ConfiguredMailSecurity, + + /// How to check IMAP server TLS certificates. ConfiguredImapCertificateChecks, /// Configured SMTP server hostname. @@ -208,14 +208,26 @@ pub enum Config { /// Configured SMTP server port. ConfiguredSendPort, + + /// How to check SMTP server TLS certificates. ConfiguredSmtpCertificateChecks, /// Whether OAuth 2 is used with configured provider. ConfiguredServerFlags, + + /// Configured SMTP server security (e.g. TLS, STARTTLS). ConfiguredSendSecurity, + + /// Configured folder for incoming messages. ConfiguredInboxFolder, + + /// Configured folder for chat messages. ConfiguredMvboxFolder, + + /// Configured "Sent" folder. ConfiguredSentboxFolder, + + /// Unix timestamp of the last successful configuration. ConfiguredTimestamp, /// ID of the configured provider from the provider database. @@ -228,12 +240,15 @@ pub enum Config { /// (`addr1@example.org addr2@exapmle.org addr3@example.org`) SecondaryAddrs, + /// Read-only core version string. #[strum(serialize = "sys.version")] SysVersion, + /// Maximal recommended attachment size in bytes. #[strum(serialize = "sys.msgsize_max_recommended")] SysMsgsizeMaxRecommended, + /// Space separated list of all config keys available. #[strum(serialize = "sys.config_keys")] SysConfigKeys, @@ -419,6 +434,7 @@ impl Context { Ok(()) } + /// Set the given config to a boolean value. pub async fn set_config_bool(&self, key: Config, value: bool) -> Result<()> { self.set_config(key, if value { Some("1") } else { Some("0") }) .await?; diff --git a/src/constants.rs b/src/constants.rs index f83f7d519..2775f97ef 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -66,14 +66,20 @@ pub enum KeyGenType { Ed25519 = 2, } +/// Video chat URL type. #[derive( Debug, Default, Display, Clone, Copy, PartialEq, Eq, FromPrimitive, ToPrimitive, FromSql, ToSql, )] #[repr(i8)] pub enum VideochatType { + /// Unknown type. #[default] Unknown = 0, + + /// [basicWebRTC](https://github.com/cracker0dks/basicwebrtc) instance. BasicWebrtc = 1, + + /// [Jitsi Meet](https://jitsi.org/jitsi-meet/) instance. Jitsi = 2, } @@ -109,6 +115,7 @@ pub const DC_CHAT_ID_ALLDONE_HINT: ChatId = ChatId::new(7); /// larger chat IDs are "real" chats, their messages are "real" messages. pub const DC_CHAT_ID_LAST_SPECIAL: ChatId = ChatId::new(9); +/// Chat type. #[derive( Debug, Default, @@ -127,11 +134,20 @@ pub const DC_CHAT_ID_LAST_SPECIAL: ChatId = ChatId::new(9); )] #[repr(u32)] pub enum Chattype { + /// Undefined chat type. #[default] Undefined = 0, + + /// 1:1 chat. Single = 100, + + /// Group chat. Group = 120, + + /// Mailing list. Mailinglist = 140, + + /// Broadcast list. Broadcast = 160, } diff --git a/src/events.rs b/src/events.rs index 0e8dbed45..fd7a29c03 100644 --- a/src/events.rs +++ b/src/events.rs @@ -1,7 +1,5 @@ //! # Events specification. -#![allow(missing_docs)] - use std::path::PathBuf; use async_channel::{self as channel, Receiver, Sender, TrySendError}; @@ -111,6 +109,7 @@ pub struct Event { pub typ: EventType, } +/// Event payload. #[derive(Debug, Clone, PartialEq, Eq, Serialize)] pub enum EventType { /// The library-user may write an informational string to the log. @@ -171,17 +170,23 @@ pub enum EventType { /// - Chats created, deleted or archived /// - A draft has been set /// - /// `chat_id` is set if only a single chat is affected by the changes, otherwise 0. - /// `msg_id` is set if only a single message is affected by the changes, otherwise 0. MsgsChanged { + /// Set if only a single chat is affected by the changes, otherwise 0. chat_id: ChatId, + + /// Set if only a single message is affected by the changes, otherwise 0. msg_id: MsgId, }, /// Reactions for the message changed. ReactionsChanged { + /// ID of the chat which the message belongs to. chat_id: ChatId, + + /// ID of the message for which reactions were changed. msg_id: MsgId, + + /// ID of the contact whose reaction set is changed. contact_id: ContactId, }, @@ -190,11 +195,16 @@ pub enum EventType { /// /// There is no extra #DC_EVENT_MSGS_CHANGED event send together with this event. IncomingMsg { + /// ID of the chat where the message is assigned. chat_id: ChatId, + + /// ID of the message. msg_id: MsgId, }, + /// Downloading a bunch of messages just finished. IncomingMsgBunch { + /// List of incoming message IDs. msg_ids: Vec, }, @@ -205,21 +215,30 @@ pub enum EventType { /// A single message is sent successfully. State changed from DC_STATE_OUT_PENDING to /// DC_STATE_OUT_DELIVERED, see dc_msg_get_state(). MsgDelivered { + /// ID of the chat which the message belongs to. chat_id: ChatId, + + /// ID of the message that was successfully sent. msg_id: MsgId, }, /// A single message could not be sent. State changed from DC_STATE_OUT_PENDING or DC_STATE_OUT_DELIVERED to /// DC_STATE_OUT_FAILED, see dc_msg_get_state(). MsgFailed { + /// ID of the chat which the message belongs to. chat_id: ChatId, + + /// ID of the message that could not be sent. msg_id: MsgId, }, /// A single message is read by the receiver. State changed from DC_STATE_OUT_DELIVERED to /// DC_STATE_OUT_MDN_RCVD, see dc_msg_get_state(). MsgRead { + /// ID of the chat which the message belongs to. chat_id: ChatId, + + /// ID of the message that was read. msg_id: MsgId, }, @@ -234,7 +253,10 @@ pub enum EventType { /// Chat ephemeral timer changed. ChatEphemeralTimerModified { + /// Chat ID. chat_id: ChatId, + + /// New ephemeral timer value. timer: EphemeralTimer, }, @@ -281,15 +303,15 @@ pub enum EventType { /// /// These events are typically sent after a joiner has scanned the QR code /// generated by dc_get_securejoin_qr(). - /// - /// @param data1 (int) ID of the contact that wants to join. - /// @param data2 (int) Progress as: - /// 300=vg-/vc-request received, typically shown as "bob@addr joins". - /// 600=vg-/vc-request-with-auth received, vg-member-added/vc-contact-confirm sent, typically shown as "bob@addr verified". - /// 800=contact added to chat, shown as "bob@addr securely joined GROUP". Only for the verified-group-protocol. - /// 1000=Protocol finished for this contact. SecurejoinInviterProgress { + /// ID of the contact that wants to join. contact_id: ContactId, + + /// Progress as: + /// 300=vg-/vc-request received, typically shown as "bob@addr joins". + /// 600=vg-/vc-request-with-auth received, vg-member-added/vc-contact-confirm sent, typically shown as "bob@addr verified". + /// 800=contact added to chat, shown as "bob@addr securely joined GROUP". Only for the verified-group-protocol. + /// 1000=Protocol finished for this contact. progress: usize, }, @@ -297,12 +319,13 @@ pub enum EventType { /// (Bob, the person who scans the QR code). /// The events are typically sent while dc_join_securejoin(), which /// may take some time, is executed. - /// @param data1 (int) ID of the inviting contact. - /// @param data2 (int) Progress as: - /// 400=vg-/vc-request-with-auth sent, typically shown as "alice@addr verified, introducing myself." - /// (Bob has verified alice and waits until Alice does the same for him) SecurejoinJoinerProgress { + /// ID of the inviting contact. contact_id: ContactId, + + /// Progress as: + /// 400=vg-/vc-request-with-auth sent, typically shown as "alice@addr verified, introducing myself." + /// (Bob has verified alice and waits until Alice does the same for him) progress: usize, }, @@ -312,15 +335,21 @@ pub enum EventType { /// dc_get_connectivity_html() for details. ConnectivityChanged, + /// The user's avatar changed. SelfavatarChanged, + /// Webxdc status update received. WebxdcStatusUpdate { + /// Message ID. msg_id: MsgId, + + /// Status update ID. status_update_serial: StatusUpdateSerial, }, - /// Inform that a message containing a webxdc instance has been deleted + /// Inform that a message containing a webxdc instance has been deleted. WebxdcInstanceDeleted { + /// ID of the deleted message. msg_id: MsgId, }, } diff --git a/src/headerdef.rs b/src/headerdef.rs index 6f9dd495a..ca282a81b 100644 --- a/src/headerdef.rs +++ b/src/headerdef.rs @@ -1,17 +1,18 @@ //! # List of email headers. -#![allow(missing_docs)] - use mailparse::{MailHeader, MailHeaderMap}; #[derive(Debug, Display, Clone, PartialEq, Eq, EnumVariantNames, IntoStaticStr)] #[strum(serialize_all = "kebab_case")] +#[allow(missing_docs)] pub enum HeaderDef { MessageId, Subject, Date, From_, To, + + /// Carbon copy. Cc, Disposition, @@ -34,11 +35,18 @@ pub enum HeaderDef { /// header, so it can be used to ignore such messages. XMozillaDraftInfo, + /// Mailing list ID defined in [RFC 2919](https://tools.ietf.org/html/rfc2919). ListId, ListPost, References, + + /// In-Reply-To header containing Message-ID of the parent message. InReplyTo, + + /// Used to detect mailing lists if contains "list" value + /// as described in [RFC 3834](https://tools.ietf.org/html/rfc3834) Precedence, + ContentType, ContentId, ChatVersion, @@ -52,9 +60,14 @@ pub enum HeaderDef { ChatGroupMemberRemoved, ChatGroupMemberAdded, ChatContent, + + /// Duration of the attached media file. ChatDuration, + ChatDispositionNotificationTo, ChatWebrtcRoom, + + /// [Autocrypt](https://autocrypt.org/) header. Autocrypt, AutocryptSetupMessage, SecureJoin, @@ -63,6 +76,8 @@ pub enum HeaderDef { SecureJoinInvitenumber, SecureJoinAuth, Sender, + + /// Ephemeral message timer. EphemeralTimer, Received, @@ -81,8 +96,12 @@ impl HeaderDef { } } +#[allow(missing_docs)] pub trait HeaderDefMap { + /// Returns requested header value if it exists. fn get_header_value(&self, headerdef: HeaderDef) -> Option; + + /// Returns requested header if it exists. fn get_header(&self, headerdef: HeaderDef) -> Option<&MailHeader>; } diff --git a/src/lib.rs b/src/lib.rs index 6ca085a32..9feb6329b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,4 @@ -//! # Delta Chat Core Library. +//! # Delta Chat Core Library #![recursion_limit = "256"] #![forbid(unsafe_code)] @@ -111,7 +111,7 @@ pub mod tools; pub mod accounts; pub mod reaction; -/// if set imap/incoming and smtp/outgoing MIME messages will be printed +/// If set IMAP/incoming and SMTP/outgoing MIME messages will be printed. pub const DCC_MIME_DEBUG: &str = "DCC_MIME_DEBUG"; #[cfg(test)] diff --git a/src/mimefactory.rs b/src/mimefactory.rs index e67b0aace..2672c203d 100644 --- a/src/mimefactory.rs +++ b/src/mimefactory.rs @@ -98,6 +98,8 @@ pub struct RenderedEmail { /// Message ID (Message in the sense of Email) pub rfc724_mid: String, + + /// Message subject. pub subject: String, } diff --git a/src/mimeparser.rs b/src/mimeparser.rs index 4f5f2f1bf..cf03a0100 100644 --- a/src/mimeparser.rs +++ b/src/mimeparser.rs @@ -1,7 +1,5 @@ //! # MIME message parsing module. -#![allow(missing_docs)] - use std::collections::{HashMap, HashSet}; use std::future::Future; use std::pin::Pin; @@ -130,11 +128,13 @@ pub(crate) enum MailinglistType { None, } +/// System message type. #[derive( Debug, Default, Display, Clone, Copy, PartialEq, Eq, FromPrimitive, ToPrimitive, ToSql, FromSql, )] #[repr(u32)] pub enum SystemMessage { + /// Unknown type of system message. #[default] Unknown = 0, @@ -152,8 +152,14 @@ pub enum SystemMessage { /// Autocrypt Setup Message. AutocryptSetupMessage = 6, + + /// Secure-join message. SecurejoinMessage = 7, + + /// Location streaming is enabled. LocationStreamingEnabled = 8, + + /// Location-only message. LocationOnly = 9, /// Chat ephemeral message timer is changed. @@ -1792,6 +1798,8 @@ pub struct Part { /// Size of the MIME part in bytes. pub bytes: usize, + + /// Parameters. pub param: Params, /// Attachment filename. diff --git a/src/oauth2.rs b/src/oauth2.rs index 70c58c1d2..2fe8795b4 100644 --- a/src/oauth2.rs +++ b/src/oauth2.rs @@ -1,7 +1,5 @@ //! OAuth 2 module. -#![allow(missing_docs)] - use std::collections::HashMap; use anyhow::Result; @@ -56,6 +54,8 @@ struct Response { scope: Option, } +/// Returns URL that should be opened in the browser +/// if OAuth 2 is supported for this address. pub async fn get_oauth2_url( context: &Context, addr: &str, @@ -76,7 +76,7 @@ pub async fn get_oauth2_url( } } -pub async fn get_oauth2_access_token( +pub(crate) async fn get_oauth2_access_token( context: &Context, addr: &str, code: &str, @@ -228,7 +228,11 @@ pub async fn get_oauth2_access_token( } } -pub async fn get_oauth2_addr(context: &Context, addr: &str, code: &str) -> Result> { +pub(crate) async fn get_oauth2_addr( + context: &Context, + addr: &str, + code: &str, +) -> Result> { let socks5_enabled = context.get_config_bool(Config::Socks5Enabled).await?; let oauth2 = match Oauth2::from_address(context, addr, socks5_enabled).await { Some(o) => o, diff --git a/src/pgp.rs b/src/pgp.rs index c25826cd5..6bc4bface 100644 --- a/src/pgp.rs +++ b/src/pgp.rs @@ -1,7 +1,5 @@ //! OpenPGP helper module using [rPGP facilities](https://github.com/rpgp/rpgp). -#![allow(missing_docs)] - use std::collections::{BTreeMap, HashSet}; use std::io; use std::io::Cursor; @@ -24,7 +22,10 @@ use crate::key::{DcKey, Fingerprint}; use crate::keyring::Keyring; use crate::tools::EmailAddress; +#[allow(missing_docs)] pub const HEADER_AUTOCRYPT: &str = "autocrypt-prefer-encrypt"; + +#[allow(missing_docs)] pub const HEADER_SETUPCODE: &str = "passphrase-begin"; /// A wrapper for rPGP public key types diff --git a/src/qr.rs b/src/qr.rs index b9385aede..982777b4a 100644 --- a/src/qr.rs +++ b/src/qr.rs @@ -1,7 +1,5 @@ //! # QR code module. -#![allow(missing_docs)] - mod dclogin_scheme; use std::collections::BTreeMap; @@ -37,80 +35,190 @@ const SMTP_SCHEME: &str = "SMTP:"; const HTTP_SCHEME: &str = "http://"; const HTTPS_SCHEME: &str = "https://"; +/// Scanned QR code. #[derive(Debug, Clone, PartialEq, Eq)] pub enum Qr { + /// Ask the user whether to verify the contact. + /// + /// If the user agrees, pass this QR code to [`crate::securejoin::join_securejoin`]. AskVerifyContact { + /// ID of the contact. contact_id: ContactId, + + /// Fingerprint of the contact key as scanned from the QR code. fingerprint: Fingerprint, + + /// Invite number. invitenumber: String, + + /// Authentication code. authcode: String, }, + + /// Ask the user whether to join the group. AskVerifyGroup { + /// Group name. grpname: String, + + /// Group ID. grpid: String, + + /// ID of the contact. contact_id: ContactId, + + /// Fingerprint of the contact key as scanned from the QR code. fingerprint: Fingerprint, + + /// Invite number. invitenumber: String, + + /// Authentication code. authcode: String, }, + + /// Contact fingerprint is verified. + /// + /// Ask the user if they want to start chatting. FprOk { + /// Contact ID. contact_id: ContactId, }, + + /// Scanned fingerprint does not match the last seen fingerprint. FprMismatch { + /// Contact ID. contact_id: Option, }, + + /// The scanned QR code contains a fingerprint but no e-mail address. FprWithoutAddr { + /// Key fingerprint. fingerprint: String, }, + + /// Ask the user if they want to create an account on the given domain. Account { + /// Server domain name. domain: String, }, + + /// Ask the user if they want to use the given service for video chats. WebrtcInstance { + /// Server domain name. domain: String, + + /// URL pattern for video chat rooms. instance_pattern: String, }, + + /// Contact address is scanned. + /// + /// Optionally, a draft message could be provided. + /// Ask the user if they want to start chatting. Addr { + /// Contact ID. contact_id: ContactId, + + /// Draft message. draft: Option, }, + + /// URL scanned. + /// + /// Ask the user if they want to open a browser or copy the URL to clipboard. Url { + /// URL. url: String, }, + + /// Text scanned. + /// + /// Ask the user if they want to copy the text to clipboard. Text { + /// Scanned text. text: String, }, + + /// Ask the user if they want to withdraw their own QR code. WithdrawVerifyContact { + /// Contact ID. contact_id: ContactId, + + /// Fingerprint of the contact key as scanned from the QR code. fingerprint: Fingerprint, + + /// Invite number. invitenumber: String, + + /// Authentication code. authcode: String, }, + + /// Ask the user if they want to withdraw their own group invite QR code. WithdrawVerifyGroup { + /// Group name. grpname: String, + + /// Group ID. grpid: String, + + /// Contact ID. contact_id: ContactId, + + /// Fingerprint of the contact key as scanned from the QR code. fingerprint: Fingerprint, + + /// Invite number. invitenumber: String, + + /// Authentication code. authcode: String, }, + + /// Ask the user if they want to revive their own QR code. ReviveVerifyContact { + /// Contact ID. contact_id: ContactId, + + /// Fingerprint of the contact key as scanned from the QR code. fingerprint: Fingerprint, + + /// Invite number. invitenumber: String, + + /// Authentication code. authcode: String, }, + + /// Ask the user if they want to revive their own group invite QR code. ReviveVerifyGroup { + /// Group name. grpname: String, + + /// Group ID. grpid: String, + + /// Contact ID. contact_id: ContactId, + + /// Fingerprint of the contact key as scanned from the QR code. fingerprint: Fingerprint, + + /// Invite number. invitenumber: String, + + /// Authentication code. authcode: String, }, /// `dclogin:` scheme parameters. + /// + /// Ask the user if they want to login with the email address. Login { + /// Email address. address: String, + + /// Login parameters. options: LoginOptions, }, } @@ -119,7 +227,8 @@ fn starts_with_ignore_case(string: &str, pattern: &str) -> bool { string.to_lowercase().starts_with(&pattern.to_lowercase()) } -/// Check a scanned QR code. +/// Checks a scanned QR code. +/// /// The function should be called after a QR code is scanned. /// The function takes the raw text scanned and checks what can be done with it. pub async fn check_qr(context: &Context, qr: &str) -> Result { @@ -415,6 +524,7 @@ async fn set_account_from_qr(context: &Context, qr: &str) -> Result<()> { } } +/// Sets configuration values from a QR code. pub async fn set_config_from_qr(context: &Context, qr: &str) -> Result<()> { match check_qr(context, qr).await? { Qr::Account { .. } => set_account_from_qr(context, qr).await?, @@ -617,6 +727,9 @@ async fn decode_vcard(context: &Context, qr: &str) -> Result { } impl Qr { + /// Creates a new scanned QR code of a contact address. + /// + /// May contain a message draft. pub async fn from_address( context: &Context, name: &str, diff --git a/src/qr/dclogin_scheme.rs b/src/qr/dclogin_scheme.rs index 75c084043..36118e386 100644 --- a/src/qr/dclogin_scheme.rs +++ b/src/qr/dclogin_scheme.rs @@ -9,22 +9,53 @@ use crate::context::Context; use crate::provider::Socket; use crate::{contact, login_param::CertificateChecks}; +/// Options for `dclogin:` scheme. #[derive(Debug, Clone, PartialEq, Eq)] pub enum LoginOptions { + /// Unsupported version. UnsuportedVersion(u32), + + /// Version 1. V1 { + /// IMAP server password. + /// + /// Used for SMTP if separate SMTP password is not provided. mail_pw: String, + + /// IMAP host. imap_host: Option, + + /// IMAP port. imap_port: Option, + + /// IMAP username. imap_username: Option, + + /// IMAP password. imap_password: Option, + + /// IMAP socket security. imap_security: Option, + + /// IMAP certificate checks. imap_certificate_checks: Option, + + /// SMTP host. smtp_host: Option, + + /// SMTP port. smtp_port: Option, + + /// SMTP username. smtp_username: Option, + + /// SMTP password. smtp_password: Option, + + /// SMTP socket security. smtp_security: Option, + + /// SMTP certificate checks. smtp_certificate_checks: Option, }, } diff --git a/src/qr_code_generator.rs b/src/qr_code_generator.rs index bbd2031ea..90b33d707 100644 --- a/src/qr_code_generator.rs +++ b/src/qr_code_generator.rs @@ -1,4 +1,4 @@ -#![allow(missing_docs)] +//! # QR code generation module. use anyhow::Result; use base64::Engine as _; @@ -14,6 +14,10 @@ use crate::{ securejoin, stock_str, }; +/// Returns SVG of the QR code to join the group or verify contact. +/// +/// If `chat_id` is `None`, returns verification QR code. +/// Otherwise, returns secure join QR code. pub async fn get_securejoin_qr_svg(context: &Context, chat_id: Option) -> Result { if let Some(chat_id) = chat_id { generate_join_group_qr_code(context, chat_id).await diff --git a/src/receive_imf.rs b/src/receive_imf.rs index 889cdfb73..95db04d9c 100644 --- a/src/receive_imf.rs +++ b/src/receive_imf.rs @@ -1,7 +1,5 @@ //! Internet Message Format reception pipeline. -#![allow(missing_docs)] - use std::cmp::min; use std::collections::HashSet; use std::convert::TryFrom; @@ -48,8 +46,13 @@ use crate::{contact, imap}; /// all have the same chat_id, state and sort_timestamp. #[derive(Debug)] pub struct ReceivedMsg { + /// Chat the message is assigned to. pub chat_id: ChatId, + + /// Received message state. pub state: MessageState, + + /// Message timestamp for sorting. pub sort_timestamp: i64, /// IDs of inserted rows in messages table. diff --git a/src/sql.rs b/src/sql.rs index 4fcb90ee9..6b967de17 100644 --- a/src/sql.rs +++ b/src/sql.rs @@ -1,7 +1,5 @@ //! # SQLite wrapper. -#![allow(missing_docs)] - use std::collections::{HashMap, HashSet}; use std::convert::TryFrom; use std::path::Path; @@ -26,6 +24,7 @@ use crate::peerstate::{deduplicate_peerstates, Peerstate}; use crate::stock_str; use crate::tools::{delete_file, time}; +#[allow(missing_docs)] #[macro_export] macro_rules! paramsv { () => { @@ -36,6 +35,7 @@ macro_rules! paramsv { }; } +#[allow(missing_docs)] #[macro_export] macro_rules! params_iterv { ($($param:expr),+ $(,)?) => { @@ -55,16 +55,19 @@ pub struct Sql { /// Database file path pub(crate) dbfile: PathBuf, + /// SQL connection pool. pool: RwLock>>, /// None if the database is not open, true if it is open with passphrase and false if it is /// open without a passphrase. is_encrypted: RwLock>, + /// Cache of `config` table. pub(crate) config_cache: RwLock>>, } impl Sql { + /// Creates new SQL database. pub fn new(dbfile: PathBuf) -> Sql { Self { dbfile, @@ -244,6 +247,7 @@ impl Sql { Ok(()) } + /// Updates SQL schema to the latest version. pub async fn run_migrations(&self, context: &Context) -> Result<()> { // (1) update low-level database structure. // this should be done before updates that use high-level objects that @@ -388,6 +392,7 @@ impl Sql { }) } + /// Allocates a connection from the connection pool and returns it. pub async fn get_conn( &self, ) -> Result> { @@ -585,22 +590,26 @@ impl Sql { Ok(value) } + /// Sets configuration for the given key to 32-bit signed integer value. pub async fn set_raw_config_int(&self, key: &str, value: i32) -> Result<()> { self.set_raw_config(key, Some(&format!("{value}"))).await } + /// Returns 32-bit signed integer configuration value for the given key. pub async fn get_raw_config_int(&self, key: &str) -> Result> { self.get_raw_config(key) .await .map(|s| s.and_then(|s| s.parse().ok())) } + /// Returns 32-bit unsigned integer configuration value for the given key. pub async fn get_raw_config_u32(&self, key: &str) -> Result> { self.get_raw_config(key) .await .map(|s| s.and_then(|s| s.parse().ok())) } + /// Returns boolean configuration value for the given key. pub async fn get_raw_config_bool(&self, key: &str) -> Result { // Not the most obvious way to encode bool as string, but it is matter // of backward compatibility. @@ -608,27 +617,32 @@ impl Sql { Ok(res.unwrap_or_default() > 0) } + /// Sets configuration for the given key to boolean value. pub async fn set_raw_config_bool(&self, key: &str, value: bool) -> Result<()> { let value = if value { Some("1") } else { None }; self.set_raw_config(key, value).await } + /// Sets configuration for the given key to 64-bit signed integer value. pub async fn set_raw_config_int64(&self, key: &str, value: i64) -> Result<()> { self.set_raw_config(key, Some(&format!("{value}"))).await } + /// Returns 64-bit signed integer configuration value for the given key. pub async fn get_raw_config_int64(&self, key: &str) -> Result> { self.get_raw_config(key) .await .map(|s| s.and_then(|r| r.parse().ok())) } + /// Returns configuration cache. #[cfg(feature = "internals")] pub fn config_cache(&self) -> &RwLock>> { &self.config_cache } } +/// Cleanup the account to restore some storage and optimize the database. pub async fn housekeeping(context: &Context) -> Result<()> { if let Err(err) = remove_unused_files(context).await { warn!( @@ -687,6 +701,7 @@ pub async fn housekeeping(context: &Context) -> Result<()> { Ok(()) } +/// Enumerates used files in the blobdir and removes unused ones. pub async fn remove_unused_files(context: &Context) -> Result<()> { let mut files_in_use = HashSet::new(); let mut unreferenced_count = 0; diff --git a/src/stock_str.rs b/src/stock_str.rs index f91b5a783..426f94600 100644 --- a/src/stock_str.rs +++ b/src/stock_str.rs @@ -1,7 +1,5 @@ //! Module to work with translatable stock strings. -#![allow(missing_docs)] - use std::collections::HashMap; use std::sync::Arc; @@ -21,6 +19,7 @@ use crate::message::{Message, Viewtype}; use crate::param::Param; use crate::tools::timestamp_to_str; +/// Storage for string translations. #[derive(Debug, Clone)] pub struct StockStrings { /// Map from stock string ID to the translation. @@ -35,6 +34,7 @@ pub struct StockStrings { /// See the `stock_*` methods on [Context] to use these. /// /// [Context]: crate::context::Context +#[allow(missing_docs)] #[derive(Debug, Clone, Copy, PartialEq, Eq, FromPrimitive, ToPrimitive, EnumProperty)] #[repr(u32)] pub enum StockMessage { @@ -422,6 +422,7 @@ impl Default for StockStrings { } impl StockStrings { + /// Creates a new translated string storage. pub fn new() -> Self { Self { translated_stockstrings: Arc::new(RwLock::new(Default::default())), diff --git a/src/tools.rs b/src/tools.rs index f62e4df67..8703a124f 100644 --- a/src/tools.rs +++ b/src/tools.rs @@ -124,6 +124,7 @@ pub fn timestamp_to_str(wanted: i64) -> String { } } +/// Converts duration to string representation suitable for logs. pub fn duration_to_str(duration: Duration) -> String { let secs = duration.as_secs(); let h = secs / 3600; @@ -442,6 +443,7 @@ pub(crate) async fn write_file( }) } +/// Reads the file and returns its context as a byte vector. pub async fn read_file(context: &Context, path: impl AsRef) -> Result> { let path_abs = get_abs_path(context, &path); @@ -530,7 +532,10 @@ pub(crate) fn time() -> i64 { /// ``` #[derive(Debug, PartialEq, Eq, Clone)] pub struct EmailAddress { + /// Local part of the email address. pub local: String, + + /// Email address domain. pub domain: String, } diff --git a/src/webxdc.rs b/src/webxdc.rs index eabb0684f..f4cf660a6 100644 --- a/src/webxdc.rs +++ b/src/webxdc.rs @@ -114,12 +114,12 @@ pub struct WebxdcInfo { pub struct StatusUpdateSerial(u32); impl StatusUpdateSerial { - /// Create a new [MsgId]. + /// Create a new [StatusUpdateSerial]. pub fn new(id: u32) -> StatusUpdateSerial { StatusUpdateSerial(id) } - /// Gets StatusUpdateId as untyped integer. + /// Gets StatusUpdateSerial as untyped integer. /// Avoid using this outside ffi. pub fn to_u32(self) -> u32 { self.0 From 7a6bfae93be8a036b5597c89a22f35bf31ba22db Mon Sep 17 00:00:00 2001 From: link2xt Date: Sun, 26 Jun 2022 13:51:23 +0000 Subject: [PATCH 12/13] Fix typo --- python/tests/test_0_complex_or_slow.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/tests/test_0_complex_or_slow.py b/python/tests/test_0_complex_or_slow.py index 1141fe4c7..e3decc7d6 100644 --- a/python/tests/test_0_complex_or_slow.py +++ b/python/tests/test_0_complex_or_slow.py @@ -239,7 +239,7 @@ def test_fetch_existing(acfactory, lp, mvbox_move): ac1_clone.start_io() assert_folders_configured(ac1_clone) - lp.sec("check that ac2 contact was fetchted during configure") + lp.sec("check that ac2 contact was fetched during configure") ac1_clone._evtracker.get_matching("DC_EVENT_CONTACTS_CHANGED") ac2_addr = ac2.get_config("addr") assert any(c.addr == ac2_addr for c in ac1_clone.get_contacts()) From c9db41a7f6fb6448c166559a6eb77f0d501f7250 Mon Sep 17 00:00:00 2001 From: link2xt Date: Thu, 16 Feb 2023 14:36:23 +0000 Subject: [PATCH 13/13] python: build Python 3.11 wheels --- scripts/run_all.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/run_all.sh b/scripts/run_all.sh index 2a81a2c09..7b08f0c9a 100755 --- a/scripts/run_all.sh +++ b/scripts/run_all.sh @@ -31,7 +31,7 @@ unset DCC_NEW_TMP_EMAIL # Try to build wheels for a range of interpreters, but don't fail if they are not available. # E.g. musllinux_1_1 does not have PyPy interpreters as of 2022-07-10 -tox --workdir "$TOXWORKDIR" -e py37,py38,py39,py310,pypy37,pypy38,pypy39,auditwheels --skip-missing-interpreters true +tox --workdir "$TOXWORKDIR" -e py37,py38,py39,py310,py311,pypy37,pypy38,pypy39,auditwheels --skip-missing-interpreters true echo -----------------------