Compare commits

..
Author SHA1 Message Date
link2xt f2ab9456cd python: use pyright 2023-02-20 10:33:51 +00:00
link2xt cdaef3bd0d python: do not shadow variables to fix pyright warnings 2023-02-20 10:33:16 +00:00
139 changed files with 2498 additions and 3728 deletions
+14 -14
View File
@@ -5,22 +5,22 @@ mergeable:
validate:
- do: or
validate:
- do: description
must_include:
regex: "#skip-changelog"
- do: and
validate:
- do: dependent
changed:
file: "src/**"
required: ["CHANGELOG.md"]
- do: dependent
changed:
file: "deltachat-ffi/src/**"
required: ["CHANGELOG.md"]
- do: description
must_include:
regex: '#skip-changelog'
- do: and
validate:
- do: dependent
changed:
file: 'src/**'
required: ['CHANGELOG.md']
- do: dependent
changed:
file: 'deltachat-ffi/src/**'
required: ['CHANGELOG.md']
fail:
- do: checks
status: "action_required"
status: 'action_required'
payload:
title: Changelog might need an update
summary: "Check if CHANGELOG.md needs an update or add #skip-changelog to the PR description."
+58 -83
View File
@@ -1,11 +1,5 @@
name: Rust CI
# Cancel previously started workflow runs
# when the branch is updated.
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
on:
pull_request:
push:
@@ -14,7 +8,7 @@ on:
env:
RUSTFLAGS: -Dwarnings
jobs:
lint:
name: Rustfmt and Clippy
@@ -24,7 +18,7 @@ jobs:
steps:
- uses: actions/checkout@v3
- name: Install rustfmt and clippy
run: rustup toolchain install $RUSTUP_TOOLCHAIN --profile minimal --component rustfmt --component clippy
run: rustup toolchain install $RUSTUP_TOOLCHAIN --component rustfmt --component clippy
- name: Cache rust cargo artifacts
uses: swatinem/rust-cache@v2
- name: Run rustfmt
@@ -32,25 +26,6 @@ jobs:
- name: Run clippy
run: scripts/clippy.sh
cargo_deny:
name: cargo deny
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: EmbarkStudios/cargo-deny-action@v1
with:
arguments: --all-features --workspace
command: check
command-arguments: "-Dwarnings"
provider_database:
name: Check provider database
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Check provider database
run: scripts/update-provider-database.sh
docs:
name: Rust doc comments
runs-on: ubuntu-latest
@@ -88,73 +63,73 @@ jobs:
python: 3.7
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@master
- uses: actions/checkout@master
- name: Install Rust ${{ matrix.rust }}
run: rustup toolchain install --profile minimal ${{ matrix.rust }}
- run: rustup override set ${{ matrix.rust }}
- name: Install Rust ${{ matrix.rust }}
run: rustup toolchain install ${{ matrix.rust }}
- run: rustup override set ${{ matrix.rust }}
- name: Cache rust cargo artifacts
uses: swatinem/rust-cache@v2
- name: Cache rust cargo artifacts
uses: swatinem/rust-cache@v2
- name: Check
run: cargo check --workspace --bins --examples --tests --benches
- name: Check
run: cargo check --workspace --bins --examples --tests --benches
- name: Tests
run: cargo test --workspace
- name: Tests
run: cargo test --workspace
- name: Test cargo vendor
run: cargo vendor
- name: Test cargo vendor
run: cargo vendor
- name: Install python
if: ${{ matrix.python }}
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python }}
- name: Install python
if: ${{ matrix.python }}
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python }}
- name: Install tox
if: ${{ matrix.python }}
run: pip install tox
- name: Install tox
if: ${{ matrix.python }}
run: pip install tox
- name: Build C library
if: ${{ matrix.python }}
run: cargo build -p deltachat_ffi --features jsonrpc
- name: Build C library
if: ${{ matrix.python }}
run: cargo build -p deltachat_ffi --features jsonrpc
- name: Run python tests
if: ${{ matrix.python }}
env:
DCC_NEW_TMP_EMAIL: ${{ secrets.DCC_NEW_TMP_EMAIL }}
DCC_RS_TARGET: debug
DCC_RS_DEV: ${{ github.workspace }}
working-directory: python
run: tox -e lint,mypy,doc,py3
- name: Run python tests
if: ${{ matrix.python }}
env:
DCC_NEW_TMP_EMAIL: ${{ secrets.DCC_NEW_TMP_EMAIL }}
DCC_RS_TARGET: debug
DCC_RS_DEV: ${{ github.workspace }}
working-directory: python
run: tox -e lint,mypy,doc,py3
- name: Build deltachat-rpc-server
if: ${{ matrix.python }}
run: cargo build -p deltachat-rpc-server
- name: Build deltachat-rpc-server
if: ${{ matrix.python }}
run: cargo build -p deltachat-rpc-server
- name: Add deltachat-rpc-server to path
if: ${{ matrix.python }}
run: echo ${{ github.workspace }}/target/debug >> $GITHUB_PATH
- name: Add deltachat-rpc-server to path
if: ${{ matrix.python }}
run: echo ${{ github.workspace }}/target/debug >> $GITHUB_PATH
- name: Run deltachat-rpc-client tests
if: ${{ matrix.python }}
env:
DCC_NEW_TMP_EMAIL: ${{ secrets.DCC_NEW_TMP_EMAIL }}
working-directory: deltachat-rpc-client
run: tox -e py3,lint
- name: Run deltachat-rpc-client tests
if: ${{ matrix.python }}
env:
DCC_NEW_TMP_EMAIL: ${{ secrets.DCC_NEW_TMP_EMAIL }}
working-directory: deltachat-rpc-client
run: tox -e py3,lint
- name: Install pypy
if: ${{ matrix.python }}
uses: actions/setup-python@v4
with:
python-version: "pypy${{ matrix.python }}"
- name: Install pypy
if: ${{ matrix.python }}
uses: actions/setup-python@v4
with:
python-version: 'pypy${{ matrix.python }}'
- name: Run pypy tests
if: ${{ matrix.python }}
env:
DCC_NEW_TMP_EMAIL: ${{ secrets.DCC_NEW_TMP_EMAIL }}
DCC_RS_TARGET: debug
DCC_RS_DEV: ${{ github.workspace }}
working-directory: python
run: tox -e pypy3
- name: Run pypy tests
if: ${{ matrix.python }}
env:
DCC_NEW_TMP_EMAIL: ${{ secrets.DCC_NEW_TMP_EMAIL }}
DCC_RS_TARGET: debug
DCC_RS_DEV: ${{ github.workspace }}
working-directory: python
run: tox -e pypy3
-129
View File
@@ -1,129 +0,0 @@
# Manually triggered action to build deltachat-rpc-server binaries.
name: Build deltachat-rpc-server binaries
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
on:
workflow_dispatch:
jobs:
# Build a version statically linked against musl libc
# to avoid problems with glibc version incompatibility.
build_static_linux:
name: Build deltachat-rpc-server for Linux
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v3
- name: Setup rust target
run: rustup target add x86_64-unknown-linux-musl
- name: Install musl-gcc
run: sudo apt install musl-tools
- name: Build
env:
RUSTFLAGS: "-C link-arg=-s"
run: cargo build --release --target x86_64-unknown-linux-musl -p deltachat-rpc-server --features vendored
- name: Upload binary
uses: actions/upload-artifact@v3
with:
name: deltachat-rpc-server-x86_64
path: target/x86_64-unknown-linux-musl/release/deltachat-rpc-server
if-no-files-found: error
build_aarch64_linux:
name: Cross-compile deltachat-rpc-server for aarch64 Linux
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v3
- name: Build
run: sh scripts/aarch64-unknown-linux-musl.sh
- name: Upload binary
uses: actions/upload-artifact@v3
with:
name: deltachat-rpc-server-aarch64
path: target/aarch64-unknown-linux-musl/release/deltachat-rpc-server
if-no-files-found: error
build_android:
name: Cross-compile deltachat-rpc-server for Android (armeabi-v7a, arm64-v8a, x86 and x86_64)
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v3
- uses: nttld/setup-ndk@v1
id: setup-ndk
with:
ndk-version: r21d
- name: Build
env:
ANDROID_NDK_ROOT: ${{ steps.setup-ndk.outputs.ndk-path }}
run: sh scripts/android-rpc-server.sh
- name: Upload binary
uses: actions/upload-artifact@v3
with:
name: deltachat-rpc-server-android-armv7
path: target/armv7-linux-androideabi/release/deltachat-rpc-server
if-no-files-found: error
- name: Upload binary
uses: actions/upload-artifact@v3
with:
name: deltachat-rpc-server-android-aarch64
path: target/aarch64-linux-android/release/deltachat-rpc-server
if-no-files-found: error
- name: Upload binary
uses: actions/upload-artifact@v3
with:
name: deltachat-rpc-server-android-i686
path: target/i686-linux-android/release/deltachat-rpc-server
if-no-files-found: error
- name: Upload binary
uses: actions/upload-artifact@v3
with:
name: deltachat-rpc-server-android-x86_64
path: target/x86_64-linux-android/release/deltachat-rpc-server
if-no-files-found: error
build_windows:
name: Build deltachat-rpc-server for Windows
strategy:
fail-fast: false
matrix:
include:
- os: windows-latest
artifact: win32.exe
path: deltachat-rpc-server.exe
target: i686-pc-windows-msvc
- os: windows-latest
artifact: win64.exe
path: deltachat-rpc-server.exe
target: x86_64-pc-windows-msvc
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v3
- name: Setup rust target
run: rustup target add ${{ matrix.target }}
- name: Build
run: cargo build --release --package deltachat-rpc-server --target ${{ matrix.target }} --features vendored
- name: Upload binary
uses: actions/upload-artifact@v3
with:
name: deltachat-rpc-server-${{ matrix.artifact }}
path: target/${{ matrix.target}}/release/${{ matrix.path }}
if-no-files-found: error
@@ -1,15 +1,16 @@
name: "jsonrpc js client build"
name: 'jsonrpc js client build'
on:
pull_request:
push:
tags:
- "*"
- "!py-*"
- '*'
- '!py-*'
jobs:
pack-module:
name: "Package @deltachat/jsonrpc-client and upload to download.delta.chat"
runs-on: ubuntu-20.04
name: 'Package @deltachat/jsonrpc-client and upload to download.delta.chat'
runs-on: ubuntu-18.04
steps:
- name: Install tree
run: sudo apt install tree
@@ -17,12 +18,12 @@ jobs:
uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: "16"
node-version: '16'
- name: Get tag
id: tag
uses: dawidd6/action-get-tag@v1
continue-on-error: true
- name: Get Pull Request ID
- name: Get Pullrequest ID
id: prepare
run: |
tag=${{ steps.tag.outputs.tag }}
@@ -68,9 +69,9 @@ jobs:
if: steps.upload-preview.outcome == 'success'
run: node ./node/scripts/postLinksToDetails.js
env:
URL: preview/${{ env.DELTACHAT_JSONRPC_TAR_GZ }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
MSG_CONTEXT: Download the deltachat-jsonrpc-client.tgz
URL: preview/${{ env.DELTACHAT_JSONRPC_TAR_GZ }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
MSG_CONTEXT: Download the deltachat-jsonrpc-client.tgz
# Upload to download.delta.chat/node/
- name: Upload deltachat-jsonrpc-client build to download.delta.chat/node/
if: ${{ steps.tag.outputs.tag }}
+20 -19
View File
@@ -7,25 +7,26 @@ on:
jobs:
delete:
runs-on: ubuntu-latest
steps:
- name: Get Pull Request ID
id: getid
run: |
export PULLREQUEST_ID=$(jq .number < $GITHUB_EVENT_PATH)
echo "prid=$PULLREQUEST_ID" >> $GITHUB_OUTPUT
- name: Renaming
run: |
# create empty file to copy it over the outdated deliverable on download.delta.chat
echo "This preview build is outdated and has been removed." > empty
cp empty deltachat-node-${{ steps.getid.outputs.prid }}.tar.gz
- name: Replace builds with dummy files
uses: horochx/deploy-via-scp@v1.0.1
with:
user: ${{ secrets.USERNAME }}
key: ${{ secrets.SSH_KEY }}
host: "download.delta.chat"
port: 22
local: "deltachat-node-${{ steps.getid.outputs.prid }}.tar.gz"
remote: "/var/www/html/download/node/preview/"
- name: Get Pullrequest ID
id: getid
run: |
export PULLREQUEST_ID=$(jq .number < $GITHUB_EVENT_PATH)
echo "prid=$PULLREQUEST_ID" >> $GITHUB_OUTPUT
- name: Renaming
run: |
# create empty file to copy it over the outdated deliverable on download.delta.chat
echo "This preview build is outdated and has been removed." > empty
cp empty deltachat-node-${{ steps.getid.outputs.prid }}.tar.gz
- name: Replace builds with dummy files
uses: horochx/deploy-via-scp@v1.0.1
with:
user: ${{ secrets.USERNAME }}
key: ${{ secrets.SSH_KEY }}
host: "download.delta.chat"
port: 22
local: "deltachat-node-${{ steps.getid.outputs.prid }}.tar.gz"
remote: "/var/www/html/download/node/preview/"
+20 -20
View File
@@ -9,26 +9,26 @@ jobs:
generate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v3
- name: Use Node.js 16.x
uses: actions/setup-node@v3
with:
node-version: 16.x
- name: Use Node.js 16.x
uses: actions/setup-node@v3
with:
node-version: 16.x
- name: npm install and generate documentation
run: |
cd node
npm i --ignore-scripts
npx typedoc
mv docs js
- name: npm install and generate documentation
run: |
cd node
npm i --ignore-scripts
npx typedoc
mv docs js
- name: Upload
uses: horochx/deploy-via-scp@v1.0.1
with:
user: ${{ secrets.USERNAME }}
key: ${{ secrets.KEY }}
host: "delta.chat"
port: 22
local: "node/js"
remote: "/var/www/html/"
- name: Upload
uses: horochx/deploy-via-scp@v1.0.1
with:
user: ${{ secrets.USERNAME }}
key: ${{ secrets.KEY }}
host: "delta.chat"
port: 22
local: "node/js"
remote: "/var/www/html/"
+14 -13
View File
@@ -1,10 +1,11 @@
name: "node.js build"
name: 'node.js build'
on:
pull_request:
push:
tags:
- "*"
- "!py-*"
- '*'
- '!py-*'
jobs:
prebuild:
@@ -12,13 +13,13 @@ jobs:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-20.04, macos-latest, windows-latest]
os: [ubuntu-18.04, macos-latest, windows-latest]
steps:
- name: Checkout
uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: "16"
node-version: '16'
- name: System info
run: |
rustc -vV
@@ -65,7 +66,7 @@ jobs:
pack-module:
needs: prebuild
name: Package deltachat-node and upload to download.delta.chat
runs-on: ubuntu-20.04
runs-on: ubuntu-18.04
steps:
- name: Install tree
run: sudo apt install tree
@@ -73,12 +74,12 @@ jobs:
uses: actions/checkout@v3
- uses: actions/setup-node@v2
with:
node-version: "16"
node-version: '16'
- name: Get tag
id: tag
uses: dawidd6/action-get-tag@v1
continue-on-error: true
- name: Get Pull Request ID
- name: Get Pullrequest ID
id: prepare
run: |
tag=${{ steps.tag.outputs.tag }}
@@ -99,7 +100,7 @@ jobs:
- name: Download Ubuntu prebuild
uses: actions/download-artifact@v1
with:
name: ubuntu-20.04
name: ubuntu-18.04
- name: Download macOS prebuild
uses: actions/download-artifact@v1
with:
@@ -111,11 +112,11 @@ jobs:
- shell: bash
run: |
mkdir node/prebuilds
tar -xvzf ubuntu-20.04/ubuntu-20.04.tar.gz -C node/prebuilds
tar -xvzf ubuntu-18.04/ubuntu-18.04.tar.gz -C node/prebuilds
tar -xvzf macos-latest/macos-latest.tar.gz -C node/prebuilds
tar -xvzf windows-latest/windows-latest.tar.gz -C node/prebuilds
tree node/prebuilds
rm -rf ubuntu-20.04 macos-latest windows-latest
rm -rf ubuntu-18.04 macos-latest windows-latest
- name: Install dependencies without running scripts
run: |
npm install --ignore-scripts
@@ -151,8 +152,8 @@ jobs:
if: steps.upload-preview.outcome == 'success'
run: node ./node/scripts/postLinksToDetails.js
env:
URL: preview/${{ env.DELTACHAT_NODE_TAR_GZ }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
URL: preview/${{ env.DELTACHAT_NODE_TAR_GZ }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# Upload to download.delta.chat/node/
- name: Upload deltachat-node build to download.delta.chat/node/
if: ${{ steps.tag.outputs.tag }}
+7 -12
View File
@@ -1,16 +1,11 @@
name: "node.js tests"
# Cancel previously started workflow runs
# when the branch is updated.
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
name: 'node.js tests'
on:
pull_request:
push:
branches:
- master
- staging
- trying
jobs:
tests:
@@ -18,13 +13,13 @@ jobs:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
os: [ubuntu-18.04, macos-latest, windows-latest]
steps:
- name: Checkout
uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: "16"
node-version: '16'
- name: System info
run: |
rustc -vV
@@ -64,7 +59,7 @@ jobs:
npm run test
env:
DCC_NEW_TMP_EMAIL: ${{ secrets.DCC_NEW_TMP_EMAIL }}
NODE_OPTIONS: "--force-node-api-uncaught-exceptions-policy=true"
NODE_OPTIONS: '--force-node-api-uncaught-exceptions-policy=true'
- name: Run tests on Windows, except lint
timeout-minutes: 10
if: runner.os == 'Windows'
@@ -73,4 +68,4 @@ jobs:
npm run test:mocha
env:
DCC_NEW_TMP_EMAIL: ${{ secrets.DCC_NEW_TMP_EMAIL }}
NODE_OPTIONS: "--force-node-api-uncaught-exceptions-policy=true"
NODE_OPTIONS: '--force-node-api-uncaught-exceptions-policy=true'
+8 -8
View File
@@ -11,13 +11,13 @@ jobs:
name: Build REPL example
runs-on: windows-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v3
- name: Build
run: cargo build -p deltachat-repl --features vendored
- name: Build
run: cargo build -p deltachat-repl --features vendored
- name: Upload binary
uses: actions/upload-artifact@v3
with:
name: repl.exe
path: "target/debug/deltachat-repl.exe"
- name: Upload binary
uses: actions/upload-artifact@v3
with:
name: repl.exe
path: 'target/debug/deltachat-repl.exe'
+14 -12
View File
@@ -8,18 +8,20 @@ on:
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Build the documentation with cargo
run: |
cargo doc --package deltachat --no-deps
- name: Upload to rs.delta.chat
uses: up9cloud/action-rsync@v1.3
env:
USER: ${{ secrets.USERNAME }}
KEY: ${{ secrets.KEY }}
HOST: "delta.chat"
SOURCE: "target/doc"
TARGET: "/var/www/html/rs/"
- uses: actions/checkout@v3
- name: Build the documentation with cargo
run: |
cargo doc --package deltachat --no-deps
- name: Upload to rs.delta.chat
uses: up9cloud/action-rsync@v1.3
env:
USER: ${{ secrets.USERNAME }}
KEY: ${{ secrets.KEY }}
HOST: "delta.chat"
SOURCE: "target/doc"
TARGET: "/var/www/html/rs/"
+14 -12
View File
@@ -8,18 +8,20 @@ on:
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Build the documentation with cargo
run: |
cargo doc --package deltachat_ffi --no-deps
- name: Upload to cffi.delta.chat
uses: up9cloud/action-rsync@v1.3
env:
USER: ${{ secrets.USERNAME }}
KEY: ${{ secrets.KEY }}
HOST: "delta.chat"
SOURCE: "target/doc"
TARGET: "/var/www/html/cffi/"
- uses: actions/checkout@v3
- name: Build the documentation with cargo
run: |
cargo doc --package deltachat_ffi --no-deps
- name: Upload to cffi.delta.chat
uses: up9cloud/action-rsync@v1.3
env:
USER: ${{ secrets.USERNAME }}
KEY: ${{ secrets.KEY }}
HOST: "delta.chat"
SOURCE: "target/doc"
TARGET: "/var/www/html/cffi/"
+13 -45
View File
@@ -1,42 +1,11 @@
# Changelog
## 1.111.0
### Changes
- Make smeared timestamp generation non-async. #4075
- Set minimum TLS version to 1.2. #4096
- Run `cargo-deny` in CI. #4101
- Check provider database with CI. #4099
- Switch to DEFERRED transactions #4100
### Fixes
- Do not block async task executor while decrypting the messages. #4079
- Housekeeping: delete the blobs backup dir #4123
### API-Changes
- jsonrpc: add more advanced API to send a message. #4097
- jsonrpc: add get webxdc blob API `getWebxdcBlob` #4070
## 1.110.0
## Unreleased
### Changes
- use transaction in `Contact::add_or_lookup()` #4059
- Organize the connection pool as a stack rather than a queue to ensure that
connection page cache is reused more often.
This speeds up tests by 28%, real usage will have lower speedup. #4065
- Use transaction in `update_blocked_mailinglist_contacts`. #4058
- Remove `Sql.get_conn()` interface in favor of `.call()` and `.transaction()`. #4055
- Updated provider database.
- Disable DKIM-Checks again #4076
- Switch from "X.Y.Z" and "py-X.Y.Z" to "vX.Y.Z" tags. #4089
- mimeparser: handle headers from the signed part of unencrypted signed message #4013
### Fixes
- Start SQL transactions with IMMEDIATE behaviour rather than default DEFERRED one. #4063
- Fix a problem with Gmail where (auto-)deleted messages would get archived instead of deleted.
Move them to the Trash folder for Gmail which auto-deletes trashed messages in 30 days #3972
- Clear config cache after backup import. This bug sometimes resulted in the import to seemingly work at first. #4067
- Update timestamps in `param` columns with transactions. #4083
### API-Changes
@@ -70,7 +39,6 @@
- Prefer TLS over STARTTLS during autoconfiguration #4021
- Use SOCKS5 configuration for HTTP requests #4017
- Show non-deltachat emails by default for new installations #4019
- Re-enabled SMTP pipelining after disabling it in #4006
### Fixes
- Fix Securejoin for multiple devices on a joining side #3982
@@ -102,7 +70,7 @@
### Fixes
- Securejoin: Fix adding and handling Autocrypt-Gossip headers #3914
- fix verifier-by addr was empty string instead of None #3961
- fix verifier-by addr was empty string intead of None #3961
- Emit DC_EVENT_MSGS_CHANGED for DC_CHAT_ID_ARCHIVED_LINK when the number of archived chats with
unread messages increases #3959
- Fix Peerstate comparison #3962
@@ -203,7 +171,7 @@
- jsonrpc: Add async Python client #3734
### Fixes
- Make sure malformed messages will never block receiving further messages anymore #3771
- Make sure malformed messsages will never block receiving further messages anymore #3771
- strip leading/trailing whitespace from "Chat-Group-Name{,-Changed}:" headers content #3650
- Assume all Thunderbird users prefer encryption #3774
- refactor peerstate handling to ensure no duplicate peerstates #3776
@@ -355,7 +323,7 @@
- `importBackup()`
- `getMessageHtml()` #3671
- `miscGetStickerFolder` and `miscGetStickers` #3672
- breaking: jsonrpc: remove function `messageListGetMessageIds()`, it is replaced by `getMessageIds()` and `getMessageListItems()` the latter returns a new `MessageListItem` type, which is the now preferred way of using the message list.
- breaking: jsonrpc: remove function `messageListGetMessageIds()`, it is replaced by `getMessageIds()` and `getMessageListItems()` the latter returns a new `MessageListItem` type, which is the now prefered way of using the message list.
- jsonrpc: add type: #3641, #3645
- `MessageSearchResult`
- `Location`
@@ -381,7 +349,7 @@
- jsonrpc js client:
- Change package name from `deltachat-jsonrpc-client` to `@deltachat/jsonrpc-client`
- remove relative file dependency to it from `deltachat-node` (because it did not work anyway and broke the nix build of desktop)
- ci: add github ci action to upload it to our download server automatically on release
- ci: add github ci action to upload it to our download server automaticaly on realease
## 1.95.0
@@ -458,7 +426,7 @@
- Auto accept contact requests if `Config::Bot` is set for a client #3567
- Don't prepend the subject to chat messages in mailinglists
- fix `set_core_version.py` script to also update version in `deltachat-jsonrpc/typescript/package.json` #3585
- Reject webxdc-updates from contacts who are not group members #3568
- Reject webxcd-updates from contacts who are not group members #3568
## 1.93.0
@@ -681,7 +649,7 @@
### Fixes
- node: throw error when getting context with an invalid account id
- node: throw error when instantiating a wrapper class on `null` (Context, Message, Chat, ChatList and so on)
- node: throw error when instanciating a wrapper class on `null` (Context, Message, Chat, ChatList and so on)
- use same contact-color if email address differ only in upper-/lowercase #3327
- repair encrypted mails "mixed up" by Google Workspace "Append footer" function #3315
@@ -769,7 +737,7 @@
- hopefully fix a bug where outgoing messages appear twice with Amazon SES #3077
- do not delete messages without Message-IDs as duplicates #3095
- assign replies from a different email address to the correct chat #3119
- assign outgoing private replies to the correct chat #3177
- assing outgoing private replies to the correct chat #3177
- start ephemeral timer when seen status is synchronized via IMAP #3122
- do not create empty contact requests with "setup changed" messages;
instead, send a "setup changed" message into all chats we share with the peer #3187
@@ -822,7 +790,7 @@
- don't watch Sent folder by default #3025
- use webxdc app name in chatlist/quotes/replies etc. #3027
- make it possible to cancel message sending by removing the message #3034,
this was previously removed in 1.71.0 #2939
this was previosuly removed in 1.71.0 #2939
- synchronize Seen flags only on watched folders to speed up
folder scanning #3041
- remove direct dependency on `byteorder` crate #3031
@@ -2044,7 +2012,7 @@
- #1043 avoid potential crashes in malformed From/Chat-Disposition... headers
- #1045 #1041 #1038 #1035 #1034 #1029 #1025 various cleanups and doc
improvements
improvments
## 1.0.0-beta.16
@@ -2123,7 +2091,7 @@
- trigger reconnect more often on imap error states. Should fix an
issue observed when trying to empty a folder. @hpk42
- un-split qr tests: we fixed qr-securejoin protocol flakiness
- un-split qr tests: we fixed qr-securejoin protocol flakyness
last weeks. @hpk42
## 1.0.0-beta.10
@@ -2161,7 +2129,7 @@
- fix moving self-sent messages, thanks @r10s, @flub, @hpk42
- fix flakiness/sometimes-failing verified/join-protocols,
- fix flakyness/sometimes-failing verified/join-protocols,
thanks @flub, @r10s, @hpk42
- fix reply-to-encrypted message to keep encryption
@@ -2180,7 +2148,7 @@
- fixes imap-protocol parsing bugs that lead to infinitely
repeated crashing while trying to receive messages with
a subject that contained non-utf8. thanks @link2xt
a subjec that contained non-utf8. thanks @link2xt
- fixed logic to find encryption subkey -- previously
delta chat would use the primary key for encryption
Generated
+459 -507
View File
File diff suppressed because it is too large Load Diff
+5 -5
View File
@@ -1,6 +1,6 @@
[package]
name = "deltachat"
version = "1.111.0"
version = "1.109.0"
edition = "2021"
license = "MPL-2.0"
rust-version = "1.63"
@@ -17,12 +17,11 @@ opt-level = 0
# This does not apply to crates in the workspace.
# <https://doc.rust-lang.org/cargo/reference/profiles.html#overrides>
[profile.dev.package."*"]
opt-level = "z"
opt-level = 3
[profile.release]
lto = true
panic = 'abort'
opt-level = "z"
[dependencies]
deltachat_derive = { path = "./deltachat_derive" }
@@ -32,13 +31,14 @@ ratelimit = { path = "./deltachat-ratelimit" }
anyhow = "1"
async-channel = "1.8.0"
async-imap = { git = "https://github.com/async-email/async-imap", branch = "master", default-features = false, features = ["runtime-tokio"] }
async-native-tls = { version = "0.5", default-features = false, features = ["runtime-tokio"] }
async-native-tls = { version = "0.4", default-features = false, features = ["runtime-tokio"] }
async-smtp = { version = "0.8", default-features = false, features = ["runtime-tokio"] }
async_zip = { version = "0.0.9", default-features = false, features = ["deflate"] }
backtrace = "0.3"
base64 = "0.21"
bitflags = "1.3"
chrono = { version = "0.4", default-features=false, features = ["clock", "std"] }
crossbeam-queue = "0.3"
email = { git = "https://github.com/deltachat/rust-email", branch = "master" }
encoded-words = { git = "https://github.com/async-email/encoded-words", branch = "master" }
escaper = "0.1"
@@ -52,12 +52,12 @@ kamadak-exif = "0.5"
lettre_email = { git = "https://github.com/deltachat/lettre", branch = "master" }
libc = "0.2"
mailparse = "0.14"
native-tls = "0.2"
num_cpus = "1.15"
num-derive = "0.3"
num-traits = "0.2"
once_cell = "1.17.0"
percent-encoding = "2.2"
parking_lot = "0.12"
pgp = { version = "0.9", default-features = false }
pretty_env_logger = { version = "0.4", optional = true }
qrcodegen = "1.7.0"
+2 -2
View File
@@ -1,6 +1,6 @@
[package]
name = "deltachat_ffi"
version = "1.111.0"
version = "1.109.0"
description = "Deltachat FFI"
edition = "2018"
readme = "README.md"
@@ -29,5 +29,5 @@ once_cell = "1.17.0"
[features]
default = ["vendored"]
vendored = ["deltachat/vendored"]
jsonrpc = ["dep:deltachat-jsonrpc"]
jsonrpc = ["deltachat-jsonrpc"]
+9 -9
View File
@@ -612,7 +612,7 @@ int dc_all_work_done (dc_context_t* context);
* While dc_configure() returns immediately,
* the started configuration-job may take a while.
*
* During configuration, #DC_EVENT_CONFIGURE_PROGRESS events are emitted;
* During configuration, #DC_EVENT_CONFIGURE_PROGRESS events are emmited;
* they indicate a successful configuration as well as errors
* and may be used to create a progress bar.
*
@@ -869,7 +869,7 @@ uint32_t dc_get_chat_id_by_contact_id (dc_context_t* context, uint32_t co
* @param context The context object as returned from dc_context_new().
* @param chat_id The chat ID to send the message to.
* @param msg The message object to send to the chat defined by the chat ID.
* On success, msg_id and state of the object are set up,
* On succcess, msg_id and state of the object are set up,
* The function does not take ownership of the object,
* so you have to free it using dc_msg_unref() as usual.
* @return The ID of the message that is being prepared.
@@ -880,7 +880,7 @@ uint32_t dc_prepare_msg (dc_context_t* context, uint32_t ch
/**
* Send a message defined by a dc_msg_t object to a chat.
*
* Sends the event #DC_EVENT_MSGS_CHANGED on success.
* 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
* view, you're done with the message. Sooner or later it will find its way.
@@ -909,7 +909,7 @@ uint32_t dc_prepare_msg (dc_context_t* context, uint32_t ch
* @param chat_id The chat ID to send the message to.
* If dc_prepare_msg() was called before, this parameter can be 0.
* @param msg The message object to send to the chat defined by the chat ID.
* On success, msg_id of the object is set up,
* On succcess, msg_id of the object is set up,
* The function does not take ownership of the object,
* so you have to free it using dc_msg_unref() as usual.
* @return The ID of the message that is about to be sent. 0 in case of errors.
@@ -926,7 +926,7 @@ uint32_t dc_send_msg (dc_context_t* context, uint32_t ch
* @param chat_id The chat ID to send the message to.
* If dc_prepare_msg() was called before, this parameter can be 0.
* @param msg The message object to send to the chat defined by the chat ID.
* On success, msg_id of the object is set up,
* On succcess, msg_id of the object is set up,
* The function does not take ownership of the object,
* so you have to free it using dc_msg_unref() as usual.
* @return The ID of the message that is about to be sent. 0 in case of errors.
@@ -937,7 +937,7 @@ uint32_t dc_send_msg_sync (dc_context_t* context, uint32
/**
* Send a simple text message a given chat.
*
* Sends the event #DC_EVENT_MSGS_CHANGED on success.
* 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
* view, you're done with the message. Sooner or later it will find its way.
@@ -1146,7 +1146,7 @@ void dc_set_draft (dc_context_t* context, uint32_t ch
* // not now and not when this code is executed again
* dc_add_device_msg(context, "update-123", NULL);
* } else {
* // welcome message was not added now, this is an older installation,
* // welcome message was not added now, this is an oder installation,
* // add a changelog
* dc_add_device_msg(context, "update-123", changelog_msg);
* }
@@ -3184,7 +3184,7 @@ dc_lot_t* dc_chatlist_get_summary (const dc_chatlist_t* chatlist, siz
* it takes the chat ID and the message ID as returned by dc_chatlist_get_chat_id() and dc_chatlist_get_msg_id()
* as arguments. The chatlist object itself is not needed directly.
*
* This maybe useful if you convert the complete object into a different representation
* This maybe useful if you convert the complete object into a different represenation
* as done e.g. 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.
@@ -6341,7 +6341,7 @@ void dc_event_unref(dc_event_t* event);
///
/// Used in status messages.
///
/// @deprecated 2022-09-10
/// @deperecated 2022-09-10
#define DC_STR_EPHEMERAL_MINUTE 77
/// "Message deletion timer is set to 1 hour."
+27 -16
View File
@@ -2661,7 +2661,7 @@ pub unsafe fn dc_array_is_independent(
///
/// This is the structure behind [dc_chatlist_t] which is the opaque
/// structure representing a chatlist in the FFI API. It exists
/// because the FFI API has a reference from the message to the
/// because the FFI API has a refernce from the message to the
/// context, but the Rust API does not, so the FFI layer needs to glue
/// these together.
pub struct ChatlistWrapper {
@@ -2805,7 +2805,7 @@ pub unsafe extern "C" fn dc_chatlist_get_context(
///
/// This is the structure behind [dc_chat_t] which is the opaque
/// structure representing a chat in the FFI API. It exists
/// because the FFI API has a reference from the message to the
/// because the FFI API has a refernce from the message to the
/// context, but the Rust API does not, so the FFI layer needs to glue
/// these together.
pub struct ChatWrapper {
@@ -3061,7 +3061,7 @@ pub unsafe extern "C" fn dc_chat_get_info_json(
///
/// This is the structure behind [dc_msg_t] which is the opaque
/// structure representing a message in the FFI API. It exists
/// because the FFI API has a reference from the message to the
/// because the FFI API has a refernce from the message to the
/// context, but the Rust API does not, so the FFI layer needs to glue
/// these together.
pub struct MessageWrapper {
@@ -3814,7 +3814,7 @@ pub unsafe extern "C" fn dc_msg_force_plaintext(msg: *mut dc_msg_t) {
///
/// This is the structure behind [dc_contact_t] which is the opaque
/// structure representing a contact in the FFI API. It exists
/// because the FFI API has a reference from the message to the
/// because the FFI API has a refernce from the message to the
/// context, but the Rust API does not, so the FFI layer needs to glue
/// these together.
pub struct ContactWrapper {
@@ -4607,22 +4607,33 @@ mod jsonrpc {
return ptr::null_mut();
}
let account_manager = &*account_manager;
let events = block_on(account_manager.read()).get_event_emitter();
let cmd_api = deltachat_jsonrpc::api::CommandApi::from_arc(account_manager.inner.clone());
let cmd_api =
deltachat_jsonrpc::api::CommandApi::from_arc((*account_manager).inner.clone());
let (request_handle, receiver) = RpcClient::new();
let handle = RpcSession::new(request_handle.clone(), cmd_api);
let request_handle2 = request_handle.clone();
let handle = RpcSession::new(request_handle, cmd_api);
let event_thread = spawn(async move {
while let Some(event) = events.recv().await {
let event = event_to_json_rpc_notification(event);
request_handle
.send_notification("event", Some(event))
.await?;
let events = block_on({
async {
let am = (*account_manager).inner.clone();
let ev = am.read().await.get_event_emitter();
drop(am);
ev
}
});
let event_thread = spawn({
async move {
while let Some(event) = events.recv().await {
let event = event_to_json_rpc_notification(event);
request_handle2
.send_notification("event", Some(event))
.await?;
}
let res: Result<(), anyhow::Error> = Ok(());
res
}
let res: Result<(), anyhow::Error> = Ok(());
res
});
let instance = dc_jsonrpc_instance_t {
+5 -7
View File
@@ -1,6 +1,6 @@
[package]
name = "deltachat-jsonrpc"
version = "1.111.0"
version = "1.109.0"
description = "DeltaChat JSON-RPC API"
edition = "2021"
default-run = "deltachat-jsonrpc-server"
@@ -21,15 +21,14 @@ log = "0.4"
async-channel = { version = "1.8.0" }
futures = { version = "0.3.26" }
serde_json = "1.0.91"
yerpc = { version = "0.4.3", features = ["anyhow_expose"] }
yerpc = { version = "^0.4.0", features = ["anyhow_expose"] }
typescript-type-def = { version = "0.5.5", features = ["json_value"] }
tokio = { version = "1.25.0" }
sanitize-filename = "0.4"
walkdir = "2.3.2"
base64 = "0.21"
# optional dependencies
axum = { version = "0.6.6", optional = true, features = ["ws"] }
axum = { version = "0.5.9", optional = true, features = ["ws"] }
env_logger = { version = "0.10.0", optional = true }
[dev-dependencies]
@@ -37,6 +36,5 @@ tokio = { version = "1.25.0", features = ["full", "rt-multi-thread"] }
[features]
default = ["vendored"]
webserver = ["dep:env_logger", "dep:axum", "tokio/full", "yerpc/support-axum"]
vendored = ["deltachat/vendored"]
default = []
webserver = ["env_logger", "axum", "tokio/full", "yerpc/support-axum"]
+1 -1
View File
@@ -35,7 +35,7 @@ The server can be configured with environment variables:
|`DC_PORT`|`20808`|port to listen on|
|`DC_ACCOUNTS_PATH`|`./accounts`|path to storage directory|
If you are targeting other architectures (like KaiOS or Android), the webserver binary can be cross-compiled easily with [rust-cross](https://github.com/cross-rs/cross):
If you are targetting other architectures (like KaiOS or Android), the webserver binary can be cross-compiled easily with [rust-cross](https://github.com/cross-rs/cross):
```sh
cross build --features=webserver --target armv7-linux-androideabi --release
-60
View File
@@ -41,7 +41,6 @@ use types::account::Account;
use types::chat::FullChat;
use types::chat_list::ChatListEntry;
use types::contact::ContactObject;
use types::message::MessageData;
use types::message::MessageObject;
use types::provider_info::ProviderInfo;
use types::webxdc::WebxdcMessageInfo;
@@ -1463,23 +1462,6 @@ impl CommandApi {
WebxdcMessageInfo::get_for_message(&ctx, MsgId::new(instance_msg_id)).await
}
/// Get blob encoded as base64 from a webxdc message
///
/// path is the path of the file within webxdc archive
async fn get_webxdc_blob(
&self,
account_id: u32,
instance_msg_id: u32,
path: String,
) -> Result<String> {
let ctx = self.get_context(account_id).await?;
let message = Message::load_from_db(&ctx, MsgId::new(instance_msg_id)).await?;
let blob = message.get_webxdc_blob(&ctx, &path).await?;
use base64::{engine::general_purpose, Engine as _};
Ok(general_purpose::STANDARD_NO_PAD.encode(blob))
}
/// Forward messages to another chat.
///
/// All types of messages can be forwarded,
@@ -1529,48 +1511,6 @@ impl CommandApi {
Ok(message_id.to_u32())
}
async fn send_msg(&self, account_id: u32, chat_id: u32, data: MessageData) -> Result<u32> {
let ctx = self.get_context(account_id).await?;
let mut message = Message::new(if let Some(viewtype) = data.viewtype {
viewtype.into()
} else if data.file.is_some() {
Viewtype::File
} else {
Viewtype::Text
});
if data.text.is_some() {
message.set_text(data.text);
}
if data.html.is_some() {
message.set_html(data.html);
}
if data.override_sender_name.is_some() {
message.set_override_sender_name(data.override_sender_name);
}
if let Some(file) = data.file {
message.set_file(file, None);
}
if let Some((latitude, longitude)) = data.location {
message.set_location(latitude, longitude);
}
if let Some(id) = data.quoted_message_id {
message
.set_quote(
&ctx,
Some(
&Message::load_from_db(&ctx, MsgId::new(id))
.await
.context("message to quote could not be loaded")?,
),
)
.await?;
}
let msg_id = chat::send_msg(&ctx, ChatId::new(chat_id), &mut message)
.await?
.to_u32();
Ok(msg_id)
}
// ---------------------------------------------
// functions for the composer
// the composer is the message input field
@@ -502,15 +502,3 @@ impl From<ChatItem> for JSONRPCMessageListItem {
}
}
}
#[derive(Deserialize, TypeDef)]
#[serde(rename_all = "camelCase")]
pub struct MessageData {
pub text: Option<String>,
pub html: Option<String>,
pub viewtype: Option<MessageViewtype>,
pub file: Option<String>,
pub location: Option<(f64, f64)>,
pub override_sender_name: Option<String>,
pub quoted_message_id: Option<u32>,
}
@@ -8,7 +8,7 @@ use typescript_type_def::TypeDef;
pub struct ProviderInfo {
pub before_login_hint: String,
pub overview_page: String,
pub status: u32, // in reality this is an enum, but for simplicity and because it gets converted into a number anyway, we use an u32 here.
pub status: u32, // in reality this is an enum, but for simlicity and because it gets converted into a number anyway, we use an u32 here.
}
impl ProviderInfo {
-6
View File
@@ -4,9 +4,3 @@ docs
coverage
yarn*
package-lock.json
.prettierignore
example.html
report_api_coverage.mjs
scripts
dist/example
dist/test
@@ -14,7 +14,7 @@ async function run() {
throw new Error(
"USAGE: node node-add-account.js <EMAILADDRESS> <PASSWORD>"
);
console.log(`creating account for ${email}`);
console.log(`creating acccount for ${email}`);
const id = await delta.rpc.addAccount();
console.log(`created account id ${id}`);
await delta.rpc.setConfig(id, "addr", email);
+4 -11
View File
@@ -3,7 +3,7 @@
"dependencies": {
"@deltachat/tiny-emitter": "3.0.0",
"isomorphic-ws": "^4.0.1",
"yerpc": "^0.4.3"
"yerpc": "^0.3.3"
},
"devDependencies": {
"@types/chai": "^4.2.21",
@@ -14,7 +14,7 @@
"c8": "^7.10.0",
"chai": "^4.3.4",
"chai-as-promised": "^7.1.1",
"esbuild": "^0.17.9",
"esbuild": "^0.14.11",
"http-server": "^14.1.1",
"mocha": "^9.1.1",
"node-fetch": "^2.6.1",
@@ -24,19 +24,12 @@
"typescript": "^4.5.5",
"ws": "^8.5.0"
},
"exports": {
".": {
"import": "./dist/deltachat.js",
"require": "./dist/deltachat.cjs"
}
},
"license": "MPL-2.0",
"main": "dist/deltachat.js",
"name": "@deltachat/jsonrpc-client",
"scripts": {
"build": "run-s generate-bindings extract-constants build:tsc build:bundle build:cjs",
"build": "run-s generate-bindings extract-constants build:tsc build:bundle",
"build:bundle": "esbuild --format=esm --bundle dist/deltachat.js --outfile=dist/deltachat.bundle.js",
"build:cjs": "esbuild --format=cjs --bundle --packages=external dist/deltachat.js --outfile=dist/deltachat.cjs",
"build:tsc": "tsc",
"docs": "typedoc --out docs deltachat.ts",
"example": "run-s build example:build example:start",
@@ -55,5 +48,5 @@
},
"type": "module",
"types": "dist/deltachat.d.ts",
"version": "1.111.0"
"version": "1.109.0"
}
@@ -1,5 +1,5 @@
import { readFileSync } from "fs";
// only checks for the coverage of the api functions in bindings.ts for now
// only checks for the coverge of the api functions in bindings.ts for now
const generatedFile = "typescript/generated/client.ts";
const json = JSON.parse(readFileSync("./coverage/coverage-final.json"));
const jsonCoverage =
+7 -7
View File
@@ -53,7 +53,7 @@ describe("basic tests", () => {
]);
});
describe("account management", () => {
describe("account managment", () => {
it("should create account", async () => {
const res = await dc.rpc.addAccount();
assert((await dc.rpc.getAllAccountIds()).length === 1);
@@ -73,7 +73,7 @@ describe("basic tests", () => {
});
});
describe("contact management", function () {
describe("contact managment", function () {
let accountId: number;
before(async () => {
accountId = await dc.rpc.addAccount();
@@ -103,7 +103,7 @@ describe("basic tests", () => {
accountId = await dc.rpc.addAccount();
});
it("set and retrieve", async function () {
it("set and retrive", async function () {
await dc.rpc.setConfig(accountId, "addr", "valid@email");
assert((await dc.rpc.getConfig(accountId, "addr")) == "valid@email");
});
@@ -115,11 +115,11 @@ describe("basic tests", () => {
await expect(dc.rpc.getConfig(accountId, "invalid_key")).to.be.eventually
.rejected;
});
it("set and retrieve ui.*", async function () {
it("set and retrive ui.*", async function () {
await dc.rpc.setConfig(accountId, "ui.chat_bg", "color:red");
assert((await dc.rpc.getConfig(accountId, "ui.chat_bg")) == "color:red");
});
it("set and retrieve (batch)", async function () {
it("set and retrive (batch)", async function () {
const config = { addr: "valid@email", mail_pw: "1234" };
await dc.rpc.batchSetConfig(accountId, config);
const retrieved = await dc.rpc.batchGetConfig(
@@ -128,7 +128,7 @@ describe("basic tests", () => {
);
expect(retrieved).to.deep.equal(config);
});
it("set and retrieve ui.* (batch)", async function () {
it("set and retrive ui.* (batch)", async function () {
const config = {
"ui.chat_bg": "color:green",
"ui.enter_key_sends": "true",
@@ -140,7 +140,7 @@ describe("basic tests", () => {
);
expect(retrieved).to.deep.equal(config);
});
it("set and retrieve mixed(ui and core) (batch)", async function () {
it("set and retrive mixed(ui and core) (batch)", async function () {
const config = {
"ui.chat_bg": "color:yellow",
"ui.enter_key_sends": "false",
+5 -5
View File
@@ -22,7 +22,7 @@ describe("online tests", function () {
process.exit(1);
}
console.log(
"Missing DCC_NEW_TMP_EMAIL environment variable!, skip integration tests"
"Missing DCC_NEW_TMP_EMAIL environment variable!, skip intergration tests"
);
this.skip();
}
@@ -36,7 +36,7 @@ describe("online tests", function () {
account1 = await createTempUser(process.env.DCC_NEW_TMP_EMAIL);
if (!account1 || !account1.email || !account1.password) {
console.log(
"We didn't got back an account from the api, skip integration tests"
"We didn't got back an account from the api, skip intergration tests"
);
this.skip();
}
@@ -44,7 +44,7 @@ describe("online tests", function () {
account2 = await createTempUser(process.env.DCC_NEW_TMP_EMAIL);
if (!account2 || !account2.email || !account2.password) {
console.log(
"We didn't got back an account2 from the api, skip integration tests"
"We didn't got back an account2 from the api, skip intergration tests"
);
this.skip();
}
@@ -74,7 +74,7 @@ describe("online tests", function () {
accountsConfigured = true;
});
it("send and receive text message", async function () {
it("send and recieve text message", async function () {
if (!accountsConfigured) {
this.skip();
}
@@ -106,7 +106,7 @@ describe("online tests", function () {
expect(message.text).equal("Hello");
});
it("send and receive text message roundtrip, encrypted on answer onwards", async function () {
it("send and recieve text message roundtrip, encrypted on answer onwards", async function () {
if (!accountsConfigured) {
this.skip();
}
+2 -3
View File
@@ -1,7 +1,6 @@
[package]
name = "deltachat-repl"
version = "1.111.0"
license = "MPL-2.0"
version = "1.109.0"
edition = "2021"
[dependencies]
@@ -12,7 +11,7 @@ dirs = "4"
log = "0.4.16"
pretty_env_logger = "0.4"
rusqlite = "0.28"
rustyline = "11"
rustyline = "10"
tokio = { version = "1", features = ["fs", "rt-multi-thread", "macros"] }
[features]
+2 -2
View File
@@ -29,7 +29,7 @@ use tokio::fs;
/// Reset database tables.
/// Argument is a bitmask, executing single or multiple actions in one call.
/// e.g. bitmask 7 triggers actions defined with bits 1, 2 and 4.
/// e.g. bitmask 7 triggers actions definded with bits 1, 2 and 4.
async fn reset_tables(context: &Context, bits: i32) {
println!("Resetting tables ({bits})...");
if 0 != bits & 1 {
@@ -869,7 +869,7 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
if continue_streaming {
println!("Success, streaming should be continued.");
} else {
println!("Success, streaming can be stopped.");
println!("Success, streaming can be stoppped.");
}
}
"dellocations" => {
+3 -3
View File
@@ -350,8 +350,8 @@ async fn start(args: Vec<String>) -> Result<(), Error> {
match readline {
Ok(line) => {
// TODO: ignore "set mail_pw"
rl.add_history_entry(line.as_str())?;
let should_continue = Handle::current().block_on(async {
rl.add_history_entry(line.as_str());
let contine = Handle::current().block_on(async {
match handle_cmd(line.trim(), ctx.clone(), &mut selected_chat).await {
Ok(ExitResult::Continue) => true,
Ok(ExitResult::Exit) => {
@@ -365,7 +365,7 @@ async fn start(args: Vec<String>) -> Result<(), Error> {
}
});
if !should_continue {
if !contine {
break;
}
}
+1 -29
View File
@@ -25,35 +25,7 @@ deltachat_rpc_client = [
line-length = 120
[tool.ruff]
select = [
"E", "W", # pycodestyle
"F", # Pyflakes
"N", # pep8-naming
"I", # isort
"ARG", # flake8-unused-arguments
"B", # flake8-bugbear
"C4", # flake8-comprehensions
"COM", # flake8-commas
"DTZ", # flake8-datetimez
"ICN", # flake8-import-conventions
"ISC", # flake8-implicit-str-concat
"PIE", # flake8-pie
"PT", # flake8-pytest-style
"RET", # flake8-return
"SIM", # flake8-simplify
"TCH", # flake8-type-checking
"TID", # flake8-tidy-imports
"YTT", # flake8-2020
"ERA", # eradicate
"PLC", # Pylint Convention
"PLE", # Pylint Error
"PLW", # Pylint Warning
"RUF006" # asyncio-dangling-task
]
select = ["E", "F", "W", "N", "YTT", "B", "C4", "ISC", "ICN", "PT", "RET", "SIM", "TID", "ARG", "DTZ", "ERA", "PLC", "PLE", "PLW", "PIE", "COM"]
line-length = 120
[tool.isort]
@@ -27,7 +27,7 @@ def _to_attrdict(obj):
class AttrDict(dict):
"""Dictionary that allows accessing values using the "dot notation" as attributes."""
"""Dictionary that allows accessing values usin the "dot notation" as attributes."""
def __init__(self, *args, **kwargs) -> None:
super().__init__({_camel_to_snake(key): _to_attrdict(value) for key, value in dict(*args, **kwargs).items()})
@@ -1,15 +1,15 @@
from dataclasses import dataclass
from typing import TYPE_CHECKING, List, Optional, Tuple, Union
from dataclasses import dataclass
from ._utils import AttrDict
from .chat import Chat
from .const import ChatlistFlag, ContactFlag, SpecialContactId
from .contact import Contact
from .message import Message
from .rpc import Rpc
if TYPE_CHECKING:
from .deltachat import DeltaChat
from .rpc import Rpc
@dataclass
@@ -20,7 +20,7 @@ class Account:
id: int
@property
def _rpc(self) -> "Rpc":
def _rpc(self) -> Rpc:
return self.manager.rpc
async def wait_for_event(self) -> AttrDict:
@@ -158,7 +158,7 @@ class Account:
:param contact: if a contact is specified only chats including this contact are returned.
:param archived_only: if True only archived chats are returned.
:param for_forwarding: if True the chat list is sorted with "Saved messages" at the top
and without "Device chat" and contact requests.
and withot "Device chat" and contact requests.
:param no_specials: if True archive link is not added to the list.
:param alldone_hint: if True the "all done hint" special chat will be added to the list
as needed.
@@ -1,17 +1,16 @@
import calendar
from dataclasses import dataclass
from datetime import datetime
from typing import TYPE_CHECKING, Dict, List, Optional, Tuple, Union
from dataclasses import dataclass
from ._utils import AttrDict
from .const import ChatVisibility, ViewType
from .const import ChatVisibility
from .contact import Contact
from .message import Message
from .rpc import Rpc
if TYPE_CHECKING:
from datetime import datetime
from .account import Account
from .rpc import Rpc
@dataclass
@@ -22,7 +21,7 @@ class Chat:
id: int
@property
def _rpc(self) -> "Rpc":
def _rpc(self) -> Rpc:
return self.account._rpc
async def delete(self) -> None:
@@ -108,27 +107,15 @@ class Chat:
async def send_message(
self,
text: Optional[str] = None,
html: Optional[str] = None,
viewtype: Optional[ViewType] = None,
file: Optional[str] = None,
location: Optional[Tuple[float, float]] = None,
override_sender_name: Optional[str] = None,
quoted_msg: Optional[Union[int, Message]] = None,
) -> Message:
"""Send a message and return the resulting Message instance."""
if isinstance(quoted_msg, Message):
quoted_msg = quoted_msg.id
draft = {
"text": text,
"html": html,
"viewtype": viewtype,
"file": file,
"location": location,
"overrideSenderName": override_sender_name,
"quotedMsg": quoted_msg,
}
msg_id = await self._rpc.send_msg(self.account.id, self.id, draft)
msg_id, _ = await self._rpc.misc_send_msg(self.account.id, self.id, text, file, location, quoted_msg)
return Message(self.account, msg_id)
async def send_text(self, text: str) -> Message:
@@ -194,23 +181,19 @@ class Chat:
"""Add contacts to this group."""
for cnt in contact:
if isinstance(cnt, str):
contact_id = (await self.account.create_contact(cnt)).id
cnt = (await self.account.create_contact(cnt)).id
elif not isinstance(cnt, int):
contact_id = cnt.id
else:
contact_id = cnt
await self._rpc.add_contact_to_chat(self.account.id, self.id, contact_id)
cnt = cnt.id
await self._rpc.add_contact_to_chat(self.account.id, self.id, cnt)
async def remove_contact(self, *contact: Union[int, str, Contact]) -> None:
"""Remove members from this group."""
for cnt in contact:
if isinstance(cnt, str):
contact_id = (await self.account.create_contact(cnt)).id
cnt = (await self.account.create_contact(cnt)).id
elif not isinstance(cnt, int):
contact_id = cnt.id
else:
contact_id = cnt
await self._rpc.remove_contact_from_chat(self.account.id, self.id, contact_id)
cnt = cnt.id
await self._rpc.remove_contact_from_chat(self.account.id, self.id, cnt)
async def get_contacts(self) -> List[Contact]:
"""Get the contacts belonging to this chat.
@@ -234,8 +217,8 @@ class Chat:
async def get_locations(
self,
contact: Optional[Contact] = None,
timestamp_from: Optional["datetime"] = None,
timestamp_to: Optional["datetime"] = None,
timestamp_from: Optional[datetime] = None,
timestamp_to: Optional[datetime] = None,
) -> List[AttrDict]:
"""Get list of location snapshots for the given contact in the given timespan."""
time_from = calendar.timegm(timestamp_from.utctimetuple()) if timestamp_from else 0
@@ -246,9 +229,9 @@ class Chat:
locations = []
contacts: Dict[int, Contact] = {}
for loc in result:
location = AttrDict(loc)
location["chat"] = self
location["contact"] = contacts.setdefault(location.contact_id, Contact(self.account, location.contact_id))
location["message"] = Message(self.account, location.msg_id)
locations.append(location)
loc = AttrDict(loc)
loc["chat"] = self
loc["contact"] = contacts.setdefault(loc.contact_id, Contact(self.account, loc.contact_id))
loc["message"] = Message(self.account, loc.msg_id)
locations.append(loc)
return locations
@@ -2,7 +2,6 @@
import inspect
import logging
from typing import (
TYPE_CHECKING,
Callable,
Coroutine,
Dict,
@@ -14,6 +13,8 @@ from typing import (
Union,
)
from deltachat_rpc_client.account import Account
from ._utils import (
AttrDict,
parse_system_add_remove,
@@ -30,16 +31,13 @@ from .events import (
RawEvent,
)
if TYPE_CHECKING:
from deltachat_rpc_client.account import Account
class Client:
"""Simple Delta Chat client that listen to events of a single account."""
def __init__(
self,
account: "Account",
account: Account,
hooks: Optional[Iterable[Tuple[Callable, Union[type, EventFilter]]]] = None,
logger: Optional[logging.Logger] = None,
) -> None:
@@ -1,12 +1,12 @@
from dataclasses import dataclass
from typing import TYPE_CHECKING
from dataclasses import dataclass
from ._utils import AttrDict
from .rpc import Rpc
if TYPE_CHECKING:
from .account import Account
from .chat import Chat
from .rpc import Rpc
@dataclass
@@ -21,7 +21,7 @@ class Contact:
id: int
@property
def _rpc(self) -> "Rpc":
def _rpc(self) -> Rpc:
return self.account._rpc
async def block(self) -> None:
@@ -1,10 +1,8 @@
from typing import TYPE_CHECKING, Dict, List
from typing import Dict, List
from ._utils import AttrDict
from .account import Account
if TYPE_CHECKING:
from .rpc import Rpc
from .rpc import Rpc
class DeltaChat:
@@ -13,7 +11,7 @@ class DeltaChat:
This is the root of the object oriented API.
"""
def __init__(self, rpc: "Rpc") -> None:
def __init__(self, rpc: Rpc) -> None:
self.rpc = rpc
async def add_account(self) -> Account:
@@ -2,13 +2,11 @@
import inspect
import re
from abc import ABC, abstractmethod
from typing import TYPE_CHECKING, Callable, Iterable, Iterator, Optional, Set, Tuple, Union
from typing import Callable, Iterable, Iterator, Optional, Set, Tuple, Union
from ._utils import AttrDict
from .const import EventType
if TYPE_CHECKING:
from ._utils import AttrDict
def _tuple_of(obj, type_: type) -> tuple:
if not obj:
@@ -82,7 +80,7 @@ class RawEvent(EventFilter):
return (self.types, self.func) == (other.types, other.func)
return False
async def filter(self, event: "AttrDict") -> bool:
async def filter(self, event: AttrDict) -> bool:
if self.types and event.type not in self.types:
return False
return await self._call_func(event)
@@ -120,7 +118,7 @@ class NewMessage(EventFilter):
command: Optional[str] = None,
is_bot: Optional[bool] = False,
is_info: Optional[bool] = None,
func: Optional[Callable[["AttrDict"], bool]] = None,
func: Optional[Callable[[AttrDict], bool]] = None,
) -> None:
super().__init__(func=func)
self.is_bot = is_bot
@@ -159,7 +157,7 @@ class NewMessage(EventFilter):
)
return False
async def filter(self, event: "AttrDict") -> bool:
async def filter(self, event: AttrDict) -> bool:
if self.is_bot is not None and self.is_bot != event.message_snapshot.is_bot:
return False
if self.is_info is not None and self.is_info != event.message_snapshot.is_info:
@@ -201,7 +199,7 @@ class MemberListChanged(EventFilter):
return (self.added, self.func) == (other.added, other.func)
return False
async def filter(self, event: "AttrDict") -> bool:
async def filter(self, event: AttrDict) -> bool:
if self.added is not None and self.added != event.member_added:
return False
return await self._call_func(event)
@@ -233,7 +231,7 @@ class GroupImageChanged(EventFilter):
return (self.deleted, self.func) == (other.deleted, other.func)
return False
async def filter(self, event: "AttrDict") -> bool:
async def filter(self, event: AttrDict) -> bool:
if self.deleted is not None and self.deleted != event.image_deleted:
return False
return await self._call_func(event)
@@ -258,7 +256,7 @@ class GroupNameChanged(EventFilter):
return self.func == other.func
return False
async def filter(self, event: "AttrDict") -> bool:
async def filter(self, event: AttrDict) -> bool:
return await self._call_func(event)
@@ -1,13 +1,13 @@
import json
from dataclasses import dataclass
from typing import TYPE_CHECKING, Union
from dataclasses import dataclass
from ._utils import AttrDict
from .contact import Contact
from .rpc import Rpc
if TYPE_CHECKING:
from .account import Account
from .rpc import Rpc
@dataclass
@@ -18,7 +18,7 @@ class Message:
id: int
@property
def _rpc(self) -> "Rpc":
def _rpc(self) -> Rpc:
return self.account._rpc
async def send_reaction(self, *reaction: str):
@@ -1,8 +1,8 @@
import asyncio
import json
import os
from typing import AsyncGenerator, List, Optional
import asyncio
import aiohttp
import pytest_asyncio
+2 -1
View File
@@ -1,7 +1,8 @@
import asyncio
from unittest.mock import MagicMock
import pytest
import asyncio
from deltachat_rpc_client import EventType, events
from deltachat_rpc_client.rpc import JsonRpcError
@@ -1,4 +1,5 @@
import pytest
from deltachat_rpc_client import EventType
+2 -6
View File
@@ -1,6 +1,6 @@
[package]
name = "deltachat-rpc-server"
version = "1.111.0"
version = "1.109.0"
description = "DeltaChat JSON-RPC server"
edition = "2021"
readme = "README.md"
@@ -13,7 +13,7 @@ categories = ["cryptography", "std", "email"]
name = "deltachat-rpc-server"
[dependencies]
deltachat-jsonrpc = { path = "../deltachat-jsonrpc", default-features = false }
deltachat-jsonrpc = { path = "../deltachat-jsonrpc" }
anyhow = "1"
env_logger = { version = "0.10.0" }
@@ -23,7 +23,3 @@ serde_json = "1.0.91"
serde = { version = "1.0", features = ["derive"] }
tokio = { version = "1.25.0", features = ["io-std"] }
yerpc = { version = "0.4.0", features = ["anyhow_expose"] }
[features]
default = ["vendored"]
vendored = ["deltachat-jsonrpc/vendored"]
+1 -1
View File
@@ -5,7 +5,7 @@ use quote::quote;
use crate::proc_macro::TokenStream;
// For now, assume (not check) that these macros are applied to enum without
// For now, assume (not check) that these macroses are applied to enum without
// data. If this assumption is violated, compiler error will point to
// generated code, which is not very user-friendly.
-57
View File
@@ -1,57 +0,0 @@
[advisories]
unmaintained = "allow"
ignore = [
"RUSTSEC-2020-0071",
# Only affects windows if using non-default allocator (and unmaintained).
"RUSTSEC-2021-0145",
]
[bans]
# Accept some duplicate versions, ideally we work towards this list
# becoming empty. Adding versions forces us to revisit this at least
# when upgrading.
skip = [
{ name = "windows-sys", version = "<0.45" },
{ name = "wasi", version = "<0.11" },
{ name = "version_check", version = "<0.9" },
{ name = "uuid", version = "<1.3" },
{ name = "sha2", version = "<0.10" },
{ name = "rand_core", version = "<0.6" },
{ name = "rand_chacha", version = "<0.3" },
{ name = "rand", version = "<0.8" },
{ name = "nom", version = "<7.1" },
{ name = "idna", version = "<0.3" },
{ name = "humantime", version = "<2.1" },
{ name = "hermit-abi", version = "<0.3" },
{ name = "getrandom", version = "<0.2" },
{ name = "quick-error", version = "<2.0" },
{ name = "env_logger", version = "<0.10" },
{ name = "digest", version = "<0.10" },
{ name = "darling_macro", version = "<0.14" },
{ name = "darling_core", version = "<0.14" },
{ name = "darling", version = "<0.14" },
{ name = "block-buffer", version = "<0.10" },
{ name = "base64", version = "<0.21" },
]
[licenses]
allow = [
"0BSD",
"Apache-2.0",
"BSD-2-Clause",
"BSD-3-Clause",
"CC0-1.0",
"MIT",
"BSL-1.0", # Boost Software License 1.0
"Unicode-DFS-2016",
"MPL-2.0",
]
[sources.allow-org]
# Organisations which we allow git sources from.
github = [
"async-email",
"deltachat",
]
+4 -4
View File
@@ -66,13 +66,13 @@ Note that usually a mail is signed by a key that has a UID matching the from add
#### Upsides:
- With this approach, it's easy to switch to a model where the info about the transition is encoded in the PGP key. Since the key is gossiped, the information about the transition will spread virally.
- Faster transition: If you send a message to e.g. "Delta Chat Dev", all members of the "sub-group" "delta android" will know of your transition.
- Faster transation: If you send a message to e.g. "Delta Chat Dev", all members of the "sub-group" "delta android" will know of your transition.
### Alternatives and old discussions/plans:
- Change the contact instead of rewriting the group member lists. This seems to call for more trouble since we will end up with multiple contacts having the same email address.
- If needed, we could add a header a) indicating that the sender did an address transition or b) listing all the secondary (old) addresses. For now, there is no big enough benefit to warrant introducing another header and its processing on the receiver side (including all the necessary checks and handling of error cases). Instead, we only check for the `Chat-Version` header to prevent accidental transitions when an MUA user sends a message from another email address with the same key.
- If needed, we could add a header a) indicating that the sender did an address transition or b) listing all the secondary (old) addresses. For now, there is no big enough benefit to warrant introducing another header and its processing on the receiver side (including all the neccessary checks and handling of error cases). Instead, we only check for the `Chat-Version` header to prevent accidental transitions when an MUA user sends a message from another email address with the same key.
- The condition for a transition temporarily was:
@@ -107,7 +107,7 @@ The most obvious alternative would be to create a new contact with the new addre
#### Upsides:
- With this approach, it's easier to switch to a model where the info about the transition is encoded in the PGP key. Since the key is gossiped, the information about the transition will spread virally.
- (Also, less important: Slightly faster transition: If you send a message to e.g. "Delta Chat Dev", all members of the "sub-group" "delta android" will know of your transition.)
- (Also, less important: Slightly faster transation: If you send a message to e.g. "Delta Chat Dev", all members of the "sub-group" "delta android" will know of your transition.)
- It's easier to implement (if too many problems turn up, we can still switch to another approach and didn't wast that much development time.)
[full messages](https://github.com/deltachat/deltachat-core-rust/pull/2896#discussion_r852002161)
@@ -124,4 +124,4 @@ Other
Notes during implementing
========================
- As far as I understand the code, unencrypted messages are unsigned. So, the transition only works if both sides have the other side's key.
- As far as I understand the code, unencrypted messages are unsigned. So, the transition only works if both sides have the other side's key.
+1 -1
View File
@@ -62,5 +62,5 @@ Notes/Questions:
and design such that we can cover all imap flags.
- It might not be necessary to keep needs_send_mdn state in this table
- It might not be neccessary to keep needs_send_mdn state in this table
if this can be decided rather when we succeed with mark_seen/mark_delete.
+2 -2
View File
@@ -124,7 +124,7 @@ $ npm run test
```
(when using [fnm](https://github.com/Schniz/fnm) instead of nvm, you can select the architecture)
If your node and electron are already build for arm64 you can also try building for arm:
If your node and electron are already build for arm64 you can also try bulding for arm:
```
$ fnm install 16 --arch arm64
@@ -182,7 +182,7 @@ this example can also be found in the examples folder [examples/send_message.js]
### Generating Docs
We are currently migrating to automatically generated documentation.
We are curently migrating to automaticaly generated documentation.
You can find the old documentation at [old_docs](./old_docs).
to generate the documentation, run:
+1 -1
View File
@@ -2,7 +2,7 @@ import { join } from 'path'
/**
* bindings are not typed yet.
* if the available function names are required they can be found inside of `../src/module.c`
* if the availible function names are required they can be found inside of `../src/module.c`
*/
export const bindings: any = require('node-gyp-build')(join(__dirname, '../'))
+2 -2
View File
@@ -543,7 +543,7 @@ export class Context extends EventEmitter {
/**
*
* @deprecated please use `AccountManager.getSystemInfo()` instead
* @deprectated please use `AccountManager.getSystemInfo()` instead
*/
static getSystemInfo() {
return AccountManager.getSystemInfo()
@@ -818,7 +818,7 @@ export class Context extends EventEmitter {
* Must be given in number of seconds since 00:00 hours, Jan 1, 1970 UTC.
* 0 for "all up to now".
* @return Array of locations, NULL is never returned.
* The array is sorted descending;
* The array is sorted decending;
* the first entry in the array is the location with the newest timestamp.
*
* Examples:
+1 -1
View File
@@ -217,7 +217,7 @@ export class AccountManager extends EventEmitter {
/** get information about the provider
*
* This function creates a temporary context to be standalone,
* if possible use `Context.getProviderFromEmail` instead. (otherwise potential proxy settings are not used)
* if posible use `Context.getProviderFromEmail` instead. (otherwise potential proxy settings are not used)
* @deprecated
*/
static getProviderFromEmail(email: string) {
+2 -2
View File
@@ -769,7 +769,7 @@ describe('Integration tests', function () {
this.beforeAll(async function () {
if (!process.env.DCC_NEW_TMP_EMAIL) {
console.log(
'Missing DCC_NEW_TMP_EMAIL environment variable!, skip integration tests'
'Missing DCC_NEW_TMP_EMAIL environment variable!, skip intergration tests'
)
this.skip()
}
@@ -777,7 +777,7 @@ describe('Integration tests', function () {
account = await createTempUser(process.env.DCC_NEW_TMP_EMAIL)
if (!account || !account.email || !account.password) {
console.log(
"We didn't got back an account from the api, skip integration tests"
"We didn't got back an account from the api, skip intergration tests"
)
this.skip()
}
+1 -1
View File
@@ -60,5 +60,5 @@
"test:mocha": "mocha -r esm node/test/test.js --growl --reporter=spec --bail --exit"
},
"types": "node/dist/index.d.ts",
"version": "1.111.0"
"version": "1.109.0"
}
+1 -1
View File
@@ -64,7 +64,7 @@
- use new experimental full-Rust Delta Chat core
- support Autocrypt Setup Messages
- remove synchronous events
- use CircleCI for continuous integration and packaging of Linux wheels
- use CircleCI for continous integration and packaging of Linux wheels
- use docker image for building wheels
- fix code documentation links
+5 -4
View File
@@ -21,7 +21,6 @@ classifiers = [
dependencies = [
"cffi>=1.0.0",
"imap-tools",
"importlib_metadata;python_version<'3.8'",
"pluggy",
"requests",
]
@@ -44,15 +43,17 @@ deltachat = [
[tool.setuptools_scm]
root = ".."
tag_regex = '^(?P<prefix>v)?(?P<version>[^\+]+)(?P<suffix>.*)?$'
git_describe_command = "git describe --dirty --tags --long --match v*.*"
tag_regex = '^(?P<prefix>py-)?(?P<version>[^\+]+)(?P<suffix>.*)?$'
git_describe_command = "git describe --dirty --tags --long --match py-*.*"
[tool.black]
line-length = 120
[tool.ruff]
select = ["E", "F", "W", "YTT", "C4", "ISC", "ICN", "TID", "DTZ", "PLC", "PLE", "PLW", "PIE", "COM", "UP004", "UP010", "UP031", "UP032", "ANN204"]
select = ["E", "F", "W", "YTT", "C4", "ISC", "ICN", "TID", "DTZ", "PLC", "PLE", "PLW", "PIE", "COM", "UP004", "UP010", "UP031", "UP032"]
line-length = 120
[tool.isort]
profile = "black"
[tool.pyright]
+3 -6
View File
@@ -1,9 +1,6 @@
import sys
if sys.version_info >= (3, 8):
from importlib.metadata import PackageNotFoundError, version
else:
from importlib_metadata import PackageNotFoundError, version
from pkg_resources import DistributionNotFound, get_distribution
from . import capi, events, hookspec # noqa
from .account import Account, get_core_info # noqa
@@ -14,8 +11,8 @@ from .hookspec import account_hookimpl, global_hookimpl # noqa
from .message import Message # noqa
try:
__version__ = version(__name__)
except PackageNotFoundError:
__version__ = get_distribution(__name__).version
except DistributionNotFound:
# package is not installed
__version__ = "0.0.0.dev0-unknown"
+4 -4
View File
@@ -121,7 +121,7 @@ class Account:
"""re-enable logging."""
self._logging = True
def __repr__(self) -> str:
def __repr__(self):
return f"<Account path={self.db_path}>"
# def __del__(self):
@@ -159,7 +159,7 @@ class Account:
"""set stock translation string.
:param id: id of stock string (const.DC_STR_*)
:param value: string to set as new translation
:param value: string to set as new transalation
:returns: None
"""
bytestring = string.encode("utf8")
@@ -628,7 +628,7 @@ class Account:
configtracker = self.configure()
configtracker.wait_finish()
# start IO threads and configure if necessary
# start IO threads and configure if neccessary
self.start_io()
def add_account_plugin(self, plugin, name=None):
@@ -767,7 +767,7 @@ class Account:
class ScannedQRCode:
def __init__(self, dc_lot) -> None:
def __init__(self, dc_lot):
self._dc_lot = dc_lot
def is_ask_verifycontact(self):
+10 -9
View File
@@ -24,7 +24,7 @@ class Chat:
You obtain instances of it through :class:`deltachat.account.Account`.
"""
def __init__(self, account, id: int) -> None:
def __init__(self, account, id) -> None:
from .account import Account
assert isinstance(account, Account), repr(account)
@@ -463,9 +463,10 @@ class Chat:
:returns: None
"""
contact = self.account.get_contact(obj)
ret = lib.dc_remove_contact_from_chat(self.account._dc_context, self.id, contact.id)
if ret != 1:
raise ValueError(f"could not remove contact {contact!r} from chat")
if contact:
ret = lib.dc_remove_contact_from_chat(self.account._dc_context, self.id, contact.id)
if ret != 1:
raise ValueError("could not remove contact {contact!r} from chat")
def get_contacts(self):
"""get all contacts for this chat.
@@ -532,13 +533,13 @@ class Chat:
# ------ location streaming API ------------------------------
def is_sending_locations(self) -> bool:
def is_sending_locations(self):
"""return True if this chat has location-sending enabled currently.
:returns: True if location sending is enabled.
"""
return bool(lib.dc_is_sending_locations_to_chat(self.account._dc_context, self.id))
return lib.dc_is_sending_locations_to_chat(self.account._dc_context, self.id)
def enable_sending_locations(self, seconds) -> None:
def enable_sending_locations(self, seconds):
"""enable sending locations for this chat.
all subsequent messages will carry a location with them.
@@ -572,7 +573,7 @@ class Chat:
class Location:
def __init__(self, latitude, longitude, accuracy, timestamp, marker) -> None:
def __init__(self, latitude, longitude, accuracy, timestamp, marker):
assert isinstance(timestamp, datetime)
self.latitude = latitude
self.longitude = longitude
@@ -580,5 +581,5 @@ class Location:
self.timestamp = timestamp
self.marker = marker
def __eq__(self, other) -> bool:
def __eq__(self, other):
return self.__dict__ == other.__dict__
+3 -3
View File
@@ -15,7 +15,7 @@ class Contact:
You obtain instances of it through :class:`deltachat.account.Account`.
"""
def __init__(self, account, id) -> None:
def __init__(self, account, id):
from .account import Account
assert isinstance(account, Account), repr(account)
@@ -27,10 +27,10 @@ class Contact:
return False
return self.account._dc_context == other.account._dc_context and self.id == other.id
def __ne__(self, other) -> bool:
def __ne__(self, other):
return not self == other
def __repr__(self) -> str:
def __repr__(self):
return f"<Contact id={self.id} addr={self.addr} dc_context={self.account._dc_context}>"
@property
+2 -2
View File
@@ -147,7 +147,7 @@ class DirectImap:
log("Message", msg.uid, "has empty body")
continue
path = pathlib.Path(str(dir)).joinpath("IMAP", self.logid, imapfolder)
path = pathlib.Path(str(dir)).joinpath("IMAP", str(self.logid), imapfolder)
path.mkdir(parents=True, exist_ok=True)
fn = path.joinpath(str(msg.uid))
fn.write_bytes(body)
@@ -191,7 +191,7 @@ class DirectImap:
class IdleManager:
def __init__(self, direct_imap) -> None:
def __init__(self, direct_imap):
self.direct_imap = direct_imap
self.log = direct_imap.account.log
# fetch latest messages before starting idle so that it only
+6 -7
View File
@@ -25,12 +25,12 @@ def get_dc_event_name(integer, _DC_EVENTNAME_MAP={}):
class FFIEvent:
def __init__(self, name: str, data1, data2) -> None:
def __init__(self, name: str, data1, data2):
self.name = name
self.data1 = data1
self.data2 = data2
def __str__(self) -> str:
def __str__(self):
if self.name == "DC_EVENT_INFO":
return f"INFO {self.data2}"
if self.name == "DC_EVENT_WARNING":
@@ -84,10 +84,7 @@ class FFIEventLogger:
class FFIEventTracker:
account: Account
_event_queue: Queue
def __init__(self, account: Account, timeout=None) -> None:
def __init__(self, account, timeout=None):
self.account = account
self._timeout = timeout
self._event_queue = Queue()
@@ -114,11 +111,12 @@ class FFIEventTracker:
while True:
yield self.get(timeout=timeout, check_error=check_error)
def get_matching(self, event_name_regex, check_error=True, timeout=None):
def get_matching(self, event_name_regex, check_error=True, timeout=None) -> FFIEvent:
rex = re.compile(f"^(?:{event_name_regex})$")
for ev in self.iter_events(timeout=timeout, check_error=check_error):
if rex.match(ev.name):
return ev
assert False
def get_info_contains(self, regex: str) -> FFIEvent:
rex = re.compile(regex)
@@ -126,6 +124,7 @@ class FFIEventTracker:
ev = self.get_matching("DC_EVENT_INFO")
if rex.search(ev.data2):
return ev
assert False
def get_info_regex_groups(self, regex, check_error=True):
rex = re.compile(regex)
+2 -2
View File
@@ -19,7 +19,7 @@ class Message:
:class:`deltachat.chat.Chat`.
"""
def __init__(self, account, dc_msg) -> None:
def __init__(self, account, dc_msg):
self.account = account
assert isinstance(self.account._dc_context, ffi.CData)
assert isinstance(dc_msg, ffi.CData)
@@ -33,7 +33,7 @@ class Message:
return False
return self.account == other.account and self.id == other.id
def __repr__(self) -> str:
def __repr__(self):
c = self.get_sender_contact()
typ = "outgoing" if self.is_outgoing() else "incoming"
return (
+2 -2
View File
@@ -10,14 +10,14 @@ class Reactions:
You obtain instances of it through :class:`deltachat.message.Message`.
"""
def __init__(self, account, dc_reactions) -> None:
def __init__(self, account, dc_reactions):
assert isinstance(account._dc_context, ffi.CData)
assert isinstance(dc_reactions, ffi.CData)
assert dc_reactions != ffi.NULL
self.account = account
self._dc_reactions = dc_reactions
def __repr__(self) -> str:
def __repr__(self):
return f"<Reactions dc_reactions={self._dc_reactions}>"
@classmethod
+11 -13
View File
@@ -9,7 +9,7 @@ import threading
import time
import weakref
from queue import Queue
from typing import Callable, List, Optional, Dict, Set
from typing import Callable, List, Optional
import pytest
import requests
@@ -60,13 +60,13 @@ def pytest_configure(config):
# Make sure we don't get garbled output because threads keep running
# collect all ever created accounts in a weakref-set (so we don't
# keep objects unnecessarily alive) and enable/disable logging
# keep objects unneccessarily alive) and enable/disable logging
# for each pytest test phase # (setup/call/teardown).
# Additionally make the acfactory use a logging/no-logging default.
class LoggingAspect:
def __init__(self) -> None:
self._accounts: weakref.WeakSet = weakref.WeakSet()
def __init__(self):
self._accounts = weakref.WeakSet()
@deltachat.global_hookimpl
def dc_account_init(self, account):
@@ -129,7 +129,7 @@ def pytest_report_header(config, startdir):
if cfg:
if "?" in cfg:
url, token = cfg.split("?", 1)
summary.append(f"Liveconfig provider: {url}?<token omitted>")
summary.append(f"Liveconfig provider: {url}?<token ommitted>")
else:
summary.append(f"Liveconfig file: {cfg}")
return summary
@@ -143,12 +143,10 @@ def testprocess(request):
class TestProcess:
"""A pytest session-scoped instance to help with managing "live" account configurations."""
_addr2files: Dict[str, Dict[pathlib.Path, bytes]]
def __init__(self, pytestconfig) -> None:
def __init__(self, pytestconfig):
self.pytestconfig = pytestconfig
self._addr2files = {}
self._configlist: List[Dict[str, str]] = []
self._configlist = []
def get_liveconfig_producer(self):
"""provide live account configs, cached on a per-test-process scope
@@ -252,7 +250,7 @@ def data(request):
fn = os.path.join(path, *bn.split("/"))
if os.path.exists(fn):
return fn
print(f"WARNING: path does not exist: {fn!r}")
print(f"WARNING: path does not exist: {request!r}")
return None
def read_path(self, bn, mode="r"):
@@ -279,10 +277,10 @@ class ACSetup:
_configured_events: Queue
def __init__(self, testprocess, init_time) -> None:
def __init__(self, testprocess, init_time):
self._configured_events = Queue()
self._account2state: Dict[Account, str] = {}
self._imap_cleaned: Set[str] = set()
self._account2state = {}
self._imap_cleaned = set()
self.testprocess = testprocess
self.init_time = init_time
+4 -16
View File
@@ -1,25 +1,19 @@
from queue import Queue
from threading import Event
from typing import List, TYPE_CHECKING
from .hookspec import Global, account_hookimpl
if TYPE_CHECKING:
from .events import FFIEvent
class ImexFailed(RuntimeError):
"""Exception for signalling that import/export operations failed."""
class ImexTracker:
_imex_events: Queue
def __init__(self) -> None:
def __init__(self):
self._imex_events = Queue()
@account_hookimpl
def ac_process_ffi_event(self, ffi_event: "FFIEvent") -> None:
def ac_process_ffi_event(self, ffi_event):
if ffi_event.name == "DC_EVENT_IMEX_PROGRESS":
self._imex_events.put(ffi_event.data1)
elif ffi_event.name == "DC_EVENT_IMEX_FILE_WRITTEN":
@@ -56,13 +50,7 @@ class ConfigureFailed(RuntimeError):
class ConfigureTracker:
ConfigureFailed = ConfigureFailed
_configure_events: Queue
_smtp_finished: Event
_imap_finished: Event
_ffi_events: List["FFIEvent"]
_progress: Queue
def __init__(self, account) -> None:
def __init__(self, account):
self.account = account
self._configure_events = Queue()
self._smtp_finished = Event()
@@ -72,7 +60,7 @@ class ConfigureTracker:
self._gm = Global._get_plugin_manager()
@account_hookimpl
def ac_process_ffi_event(self, ffi_event: "FFIEvent") -> None:
def ac_process_ffi_event(self, ffi_event):
self._ffi_events.append(ffi_event)
if ffi_event.name == "DC_EVENT_SMTP_CONNECTED":
self._smtp_finished.set()
+2 -2
View File
@@ -77,7 +77,7 @@ class ReportType:
class AutoReplier:
def __init__(self, account, log, num_send, num_bigfiles, report_func) -> None:
def __init__(self, account, log, num_send, num_bigfiles, report_func):
self.account = account
self._log = log
self.report_func = report_func
@@ -90,7 +90,7 @@ class AutoReplier:
self._thread.setDaemon(True)
self._thread.start()
def log(self, message) -> None:
def log(self, message):
self._log(f"{self.addr} {message}")
def thread_stats(self):
+7 -43
View File
@@ -1484,7 +1484,7 @@ def test_import_export_online_all(acfactory, tmpdir, data, lp):
def test_ac_setup_message(acfactory, lp):
# note that the receiving account needs to be configured and running
# before the setup message is send. DC does not read old messages
# before ther setup message is send. DC does not read old messages
# as of Jul2019
ac1 = acfactory.new_online_configuring_account()
ac2 = acfactory.new_online_configuring_account(cloned_from=ac1)
@@ -1604,7 +1604,7 @@ def test_add_remove_member_remote_events(acfactory, lp):
in_list = queue.Queue()
class EventHolder:
def __init__(self, **kwargs) -> None:
def __init__(self, **kwargs):
self.__dict__.update(kwargs)
class InPlugin:
@@ -1954,7 +1954,6 @@ def test_immediate_autodelete(acfactory, lp):
assert msg.text == "hello"
lp.sec("ac2: wait for close/expunge on autodelete")
ac2._evtracker.get_matching("DC_EVENT_IMAP_MESSAGE_DELETED")
ac2._evtracker.get_info_contains("close/expunge succeeded")
lp.sec("ac2: check that message was autodeleted on server")
@@ -1987,48 +1986,13 @@ def test_delete_multiple_messages(acfactory, lp):
lp.sec("ac2: deleting all messages except third")
assert len(to_delete) == len(texts) - 1
ac2.delete_messages(to_delete)
ac2._evtracker.get_matching("DC_EVENT_IMAP_MESSAGE_DELETED")
ac2._evtracker.get_info_contains("close/expunge succeeded")
lp.sec("ac2: test that only one message is left")
while 1:
ac2._evtracker.get_matching("DC_EVENT_IMAP_MESSAGE_DELETED")
ac2._evtracker.get_info_contains("close/expunge succeeded")
ac2.direct_imap.select_config_folder("inbox")
nr_msgs = len(ac2.direct_imap.get_all_messages())
assert nr_msgs > 0
if nr_msgs == 1:
break
def test_trash_multiple_messages(acfactory, lp):
ac1, ac2 = acfactory.get_online_accounts(2)
ac2.set_config("delete_to_trash", "1")
chat12 = acfactory.get_accepted_chat(ac1, ac2)
lp.sec("ac1: sending 3 messages")
texts = ["first", "second", "third"]
for text in texts:
chat12.send_text(text)
lp.sec("ac2: waiting for all messages on the other side")
to_delete = []
for text in texts:
msg = ac2._evtracker.wait_next_incoming_message()
assert msg.text in texts
if text != "second":
to_delete.append(msg)
lp.sec("ac2: deleting all messages except second")
assert len(to_delete) == len(texts) - 1
ac2.delete_messages(to_delete)
lp.sec("ac2: test that only one message is left")
while 1:
ac2._evtracker.get_matching("DC_EVENT_IMAP_MESSAGE_MOVED")
ac2.direct_imap.select_config_folder("inbox")
nr_msgs = len(ac2.direct_imap.get_all_messages())
assert nr_msgs > 0
if nr_msgs == 1:
break
ac2.direct_imap.select_config_folder("inbox")
assert len(ac2.direct_imap.get_all_messages()) == 1
def test_configure_error_msgs_wrong_pw(acfactory):
+2 -1
View File
@@ -1,6 +1,7 @@
import os.path
import shutil
from filecmp import cmp
from typing import Optional
import pytest
@@ -13,7 +14,7 @@ def wait_msg_delivered(account, msg_list):
msg_list.remove((ev.data1, ev.data2))
def wait_msgs_changed(account, msgs_list):
def wait_msgs_changed(account, msgs_list: list[tuple[int, Optional[int]]]):
"""wait for one or more MSGS_CHANGED events to match msgs_list contents."""
account.log(f"waiting for msgs_list={msgs_list}")
msgs_list = list(msgs_list)
+1 -1
View File
@@ -151,7 +151,7 @@ def test_markseen_invalid_message_ids(acfactory):
ac1 = acfactory.get_pseudo_configured_account()
contact1 = ac1.create_contact("some1@example.com", name="some1")
chat = contact1.create_chat()
chat.send_text("one message")
chat.send_text("one messae")
ac1._evtracker.get_matching("DC_EVENT_MSGS_CHANGED")
msg_ids = [9]
lib.dc_markseen_msgs(ac1._dc_context, msg_ids, len(msg_ids))
+6
View File
@@ -5,6 +5,7 @@ envlist =
lint
mypy
auditwheels
pyright
[testenv]
commands =
@@ -25,6 +26,7 @@ deps =
pytest-xdist
pdbpp
requests
pyright: pyright
[testenv:.pkg]
passenv =
@@ -68,6 +70,10 @@ deps =
commands =
mypy --no-incremental src/
[testenv:pyright]
commands =
pyright
[testenv:doc]
changedir=doc
deps =
-2
View File
@@ -20,8 +20,6 @@ and an own build machine.
- `run_all.sh` builds Python wheels
- `aarch64-unknown-linux-musl.sh` cross-compiles static `deltachat-rpc-server` for aarch64
## Triggering runs on the build machine locally (fast!)
There is experimental support for triggering a remote Python or Rust test run
-18
View File
@@ -1,18 +0,0 @@
#!/bin/sh
#
# Build statically linked deltachat-rpc-server for aarch64-unknown-linux-musl.
set -x
set -e
# Download Zig
rm -fr zig-linux-x86_64-0.10.1 zig-linux-x86_64-0.10.1.tar.xz
wget https://ziglang.org/download/0.10.1/zig-linux-x86_64-0.10.1.tar.xz
tar xf zig-linux-x86_64-0.10.1.tar.xz
export PATH="$PATH:$PWD/zig-linux-x86_64-0.10.1"
cargo install cargo-zigbuild
rustup target add aarch64-unknown-linux-musl
cargo zigbuild --release --target aarch64-unknown-linux-musl -p deltachat-rpc-server --features vendored
-43
View File
@@ -1,43 +0,0 @@
#!/bin/sh
# Build deltachat-rpc-server for Android.
set -e
test -n "$ANDROID_NDK_ROOT" || exit 1
RUSTUP_TOOLCHAIN="1.64.0"
rustup install "$RUSTUP_TOOLCHAIN"
rustup target add armv7-linux-androideabi aarch64-linux-android i686-linux-android x86_64-linux-android --toolchain "$RUSTUP_TOOLCHAIN"
KERNEL="$(uname -s | tr '[:upper:]' '[:lower:]')"
ARCH="$(uname -m)"
NDK_HOST_TAG="$KERNEL-$ARCH"
TOOLCHAIN="$ANDROID_NDK_ROOT/toolchains/llvm/prebuilt/$NDK_HOST_TAG"
PACKAGE="deltachat-rpc-server"
export CARGO_PROFILE_RELEASE_LTO=on
CARGO_TARGET_ARMV7_LINUX_ANDROIDEABI_LINKER="$TOOLCHAIN/bin/armv7a-linux-androideabi16-clang" \
TARGET_CC="$TOOLCHAIN/bin/armv7a-linux-androideabi16-clang" \
TARGET_AR="$TOOLCHAIN/bin/llvm-ar" \
TARGET_RANLIB="$TOOLCHAIN/bin/llvm-ranlib" \
cargo "+$RUSTUP_TOOLCHAIN" rustc --release --target armv7-linux-androideabi -p $PACKAGE
CARGO_TARGET_AARCH64_LINUX_ANDROID_LINKER="$TOOLCHAIN/bin/aarch64-linux-android21-clang" \
TARGET_CC="$TOOLCHAIN/bin/aarch64-linux-android21-clang" \
TARGET_AR="$TOOLCHAIN/bin/llvm-ar" \
TARGET_RANLIB="$TOOLCHAIN/bin/llvm-ranlib" \
cargo "+$RUSTUP_TOOLCHAIN" rustc --release --target aarch64-linux-android -p $PACKAGE
CARGO_TARGET_I686_LINUX_ANDROID_LINKER="$TOOLCHAIN/bin/i686-linux-android16-clang" \
TARGET_CC="$TOOLCHAIN/bin/i686-linux-android16-clang" \
TARGET_AR="$TOOLCHAIN/bin/llvm-ar" \
TARGET_RANLIB="$TOOLCHAIN/bin/llvm-ranlib" \
cargo "+$RUSTUP_TOOLCHAIN" rustc --release --target i686-linux-android -p $PACKAGE
CARGO_TARGET_X86_64_LINUX_ANDROID_LINKER="$TOOLCHAIN/bin/x86_64-linux-android21-clang" \
TARGET_CC="$TOOLCHAIN/bin/x86_64-linux-android21-clang" \
TARGET_AR="$TOOLCHAIN/bin/llvm-ar" \
TARGET_RANLIB="$TOOLCHAIN/bin/llvm-ranlib" \
cargo "+$RUSTUP_TOOLCHAIN" rustc --release --target x86_64-linux-android -p $PACKAGE
+1 -1
View File
@@ -1,3 +1,3 @@
#!/bin/sh
# Run clippy for all Rust code in the project.
cargo clippy --workspace --all-targets -- -D warnings
cargo clippy --workspace --tests --examples --benches -- -D warnings
-4
View File
@@ -1,4 +0,0 @@
#!/bin/sh
codespell \
--skip './test-data,./.git,node_modules,.mypy_cache,./src/provider/data.rs,.tox,site-packages,target,Cargo.lock,*.js.map,package-lock.json,./proptest-regressions' \
--ignore-words-list crate,keypair,keypairs,iif
+347 -347
View File
@@ -1,370 +1,370 @@
resources:
- name: deltachat-core-rust
type: git
icon: github
source:
branch: master
uri: https://github.com/deltachat/deltachat-core-rust.git
- name: deltachat-core-rust
type: git
icon: github
source:
branch: master
uri: https://github.com/deltachat/deltachat-core-rust.git
- name: deltachat-core-rust-release
type: git
icon: github
source:
branch: master
uri: https://github.com/deltachat/deltachat-core-rust.git
tag_filter: "v*"
- name: deltachat-core-rust-release
type: git
icon: github
source:
branch: master
uri: https://github.com/deltachat/deltachat-core-rust.git
tag_filter: "py-*"
jobs:
- name: doxygen
plan:
- get: deltachat-core-rust
trigger: true
- name: doxygen
plan:
- get: deltachat-core-rust
trigger: true
# Build Doxygen documentation
- task: build-doxygen
config:
inputs:
- name: deltachat-core-rust
outputs:
- name: c-docs
image_resource:
source:
repository: alpine
type: registry-image
platform: linux
run:
path: sh
args:
- -ec
- |
apk add --no-cache doxygen git
cd deltachat-core-rust
scripts/run-doxygen.sh
cd ..
cp -av deltachat-core-rust/deltachat-ffi/html deltachat-core-rust/deltachat-ffi/xml c-docs/
# Build Doxygen documentation
- task: build-doxygen
config:
inputs:
- name: deltachat-core-rust
outputs:
- name: c-docs
image_resource:
source:
repository: alpine
type: registry-image
platform: linux
run:
path: sh
args:
- -ec
- |
apk add --no-cache doxygen git
cd deltachat-core-rust
scripts/run-doxygen.sh
cd ..
cp -av deltachat-core-rust/deltachat-ffi/html deltachat-core-rust/deltachat-ffi/xml c-docs/
- task: upload-c-docs
config:
inputs:
- name: c-docs
image_resource:
type: registry-image
source:
repository: alpine
platform: linux
run:
path: sh
args:
- -ec
- |
apk add --no-cache rsync openssh-client
mkdir -p ~/.ssh
chmod 700 ~/.ssh
echo "(("c.delta.chat".private_key))" > ~/.ssh/id_ed25519
chmod 600 ~/.ssh/id_ed25519
rsync -e "ssh -o StrictHostKeyChecking=no" -avz --delete c-docs/html/ delta@c.delta.chat:build-c/master
- task: upload-c-docs
config:
inputs:
- name: c-docs
image_resource:
type: registry-image
source:
repository: alpine
platform: linux
run:
path: sh
args:
- -ec
- |
apk add --no-cache rsync openssh-client
mkdir -p ~/.ssh
chmod 700 ~/.ssh
echo "(("c.delta.chat".private_key))" > ~/.ssh/id_ed25519
chmod 600 ~/.ssh/id_ed25519
rsync -e "ssh -o StrictHostKeyChecking=no" -avz --delete c-docs/html/ delta@c.delta.chat:build-c/master
- name: python-x86_64
plan:
- get: deltachat-core-rust
- get: deltachat-core-rust-release
trigger: true
- name: python-x86_64
plan:
- get: deltachat-core-rust
- get: deltachat-core-rust-release
trigger: true
# Build manylinux image with additional dependencies
- task: build-coredeps
privileged: true
config:
inputs:
# Building the latest, not tagged coredeps
- name: deltachat-core-rust
image_resource:
source:
repository: concourse/oci-build-task
type: registry-image
outputs:
- name: coredeps-image
path: image
params:
CONTEXT: deltachat-core-rust/scripts/coredeps
UNPACK_ROOTFS: "true"
BUILD_ARG_BASEIMAGE: quay.io/pypa/manylinux2014_x86_64
platform: linux
caches:
- path: cache
run:
path: build
# Build manylinux image with additional dependencies
- task: build-coredeps
privileged: true
config:
inputs:
# Building the latest, not tagged coredeps
- name: deltachat-core-rust
image_resource:
source:
repository: concourse/oci-build-task
type: registry-image
outputs:
- name: coredeps-image
path: image
params:
CONTEXT: deltachat-core-rust/scripts/coredeps
UNPACK_ROOTFS: "true"
BUILD_ARG_BASEIMAGE: quay.io/pypa/manylinux2014_x86_64
platform: linux
caches:
- path: cache
run:
path: build
# Use built image to build python wheels
- task: build-wheels
image: coredeps-image
config:
inputs:
- name: deltachat-core-rust-release
path: .
outputs:
- name: py-docs
path: ./python/doc/_build/
# Binary wheels
- name: py-wheels
path: ./python/.docker-tox/wheelhouse/
platform: linux
run:
path: bash
args:
- -exc
- |
scripts/run_all.sh
# Use built image to build python wheels
- task: build-wheels
image: coredeps-image
config:
inputs:
- name: deltachat-core-rust-release
path: .
outputs:
- name: py-docs
path: ./python/doc/_build/
# Binary wheels
- name: py-wheels
path: ./python/.docker-tox/wheelhouse/
platform: linux
run:
path: bash
args:
- -exc
- |
scripts/run_all.sh
# Upload python docs to py.delta.chat
- task: upload-py-docs
config:
inputs:
- name: py-docs
image_resource:
type: registry-image
source:
repository: alpine
platform: linux
run:
path: sh
args:
- -ec
- |
apk add --no-cache rsync openssh-client
mkdir -p ~/.ssh
chmod 700 ~/.ssh
echo "(("c.delta.chat".private_key))" > ~/.ssh/id_ed25519
chmod 600 ~/.ssh/id_ed25519
rsync -e "ssh -o StrictHostKeyChecking=no" -avz --delete py-docs/html/ delta@py.delta.chat:build/master
# Upload python docs to py.delta.chat
- task: upload-py-docs
config:
inputs:
- name: py-docs
image_resource:
type: registry-image
source:
repository: alpine
platform: linux
run:
path: sh
args:
- -ec
- |
apk add --no-cache rsync openssh-client
mkdir -p ~/.ssh
chmod 700 ~/.ssh
echo "(("c.delta.chat".private_key))" > ~/.ssh/id_ed25519
chmod 600 ~/.ssh/id_ed25519
rsync -e "ssh -o StrictHostKeyChecking=no" -avz --delete py-docs/html/ delta@py.delta.chat:build/master
# Upload x86_64 wheels and source packages
- task: upload-wheels
config:
inputs:
- name: py-wheels
image_resource:
type: registry-image
source:
repository: debian
platform: linux
run:
path: sh
args:
- -ec
- |
apt-get update -y
apt-get install -y --no-install-recommends python3-pip python3-setuptools
pip3 install devpi
devpi use https://m.devpi.net/dc/master
devpi login ((devpi.login)) --password ((devpi.password))
devpi upload py-wheels/*manylinux201*
# Upload x86_64 wheels and source packages
- task: upload-wheels
config:
inputs:
- name: py-wheels
image_resource:
type: registry-image
source:
repository: debian
platform: linux
run:
path: sh
args:
- -ec
- |
apt-get update -y
apt-get install -y --no-install-recommends python3-pip python3-setuptools
pip3 install devpi
devpi use https://m.devpi.net/dc/master
devpi login ((devpi.login)) --password ((devpi.password))
devpi upload py-wheels/*manylinux201*
- name: python-aarch64
plan:
- get: deltachat-core-rust
- get: deltachat-core-rust-release
trigger: true
- name: python-aarch64
plan:
- get: deltachat-core-rust
- get: deltachat-core-rust-release
trigger: true
# Build manylinux image with additional dependencies
- task: build-coredeps
privileged: true
config:
inputs:
# Building the latest, not tagged coredeps
- name: deltachat-core-rust
image_resource:
source:
repository: concourse/oci-build-task
type: registry-image
outputs:
- name: coredeps-image
path: image
params:
CONTEXT: deltachat-core-rust/scripts/coredeps
UNPACK_ROOTFS: "true"
BUILD_ARG_BASEIMAGE: quay.io/pypa/manylinux2014_aarch64
platform: linux
caches:
- path: cache
run:
path: build
# Build manylinux image with additional dependencies
- task: build-coredeps
privileged: true
config:
inputs:
# Building the latest, not tagged coredeps
- name: deltachat-core-rust
image_resource:
source:
repository: concourse/oci-build-task
type: registry-image
outputs:
- name: coredeps-image
path: image
params:
CONTEXT: deltachat-core-rust/scripts/coredeps
UNPACK_ROOTFS: "true"
BUILD_ARG_BASEIMAGE: quay.io/pypa/manylinux2014_aarch64
platform: linux
caches:
- path: cache
run:
path: build
# Use built image to build python wheels
- task: build-wheels
image: coredeps-image
config:
inputs:
- name: deltachat-core-rust-release
path: .
outputs:
- name: py-wheels
path: ./python/.docker-tox/wheelhouse/
platform: linux
run:
path: bash
args:
- -exc
- |
scripts/run_all.sh
# Use built image to build python wheels
- task: build-wheels
image: coredeps-image
config:
inputs:
- name: deltachat-core-rust-release
path: .
outputs:
- name: py-wheels
path: ./python/.docker-tox/wheelhouse/
platform: linux
run:
path: bash
args:
- -exc
- |
scripts/run_all.sh
# Upload aarch64 wheels
- task: upload-wheels
config:
inputs:
- name: py-wheels
image_resource:
type: registry-image
source:
repository: debian
platform: linux
run:
path: sh
args:
- -ec
- |
apt-get update -y
apt-get install -y --no-install-recommends python3-pip python3-setuptools
pip3 install devpi
devpi use https://m.devpi.net/dc/master
devpi login ((devpi.login)) --password ((devpi.password))
devpi upload py-wheels/*manylinux201*
# Upload aarch64 wheels
- task: upload-wheels
config:
inputs:
- name: py-wheels
image_resource:
type: registry-image
source:
repository: debian
platform: linux
run:
path: sh
args:
- -ec
- |
apt-get update -y
apt-get install -y --no-install-recommends python3-pip python3-setuptools
pip3 install devpi
devpi use https://m.devpi.net/dc/master
devpi login ((devpi.login)) --password ((devpi.password))
devpi upload py-wheels/*manylinux201*
- name: python-musl-x86_64
plan:
- get: deltachat-core-rust
- get: deltachat-core-rust-release
trigger: true
- name: python-musl-x86_64
plan:
- get: deltachat-core-rust
- get: deltachat-core-rust-release
trigger: true
# Build manylinux image with additional dependencies
- task: build-coredeps
privileged: true
config:
inputs:
# Building the latest, not tagged coredeps
- name: deltachat-core-rust
image_resource:
source:
repository: concourse/oci-build-task
type: registry-image
outputs:
- name: coredeps-image
path: image
params:
CONTEXT: deltachat-core-rust/scripts/coredeps
UNPACK_ROOTFS: "true"
BUILD_ARG_BASEIMAGE: quay.io/pypa/musllinux_1_1_x86_64
platform: linux
caches:
- path: cache
run:
path: build
# Build manylinux image with additional dependencies
- task: build-coredeps
privileged: true
config:
inputs:
# Building the latest, not tagged coredeps
- name: deltachat-core-rust
image_resource:
source:
repository: concourse/oci-build-task
type: registry-image
outputs:
- name: coredeps-image
path: image
params:
CONTEXT: deltachat-core-rust/scripts/coredeps
UNPACK_ROOTFS: "true"
BUILD_ARG_BASEIMAGE: quay.io/pypa/musllinux_1_1_x86_64
platform: linux
caches:
- path: cache
run:
path: build
# Use built image to build python wheels
- task: build-wheels
image: coredeps-image
config:
inputs:
- name: deltachat-core-rust-release
path: .
outputs:
- name: py-wheels
path: ./python/.docker-tox/wheelhouse/
platform: linux
run:
path: bash
args:
- -exc
- |
scripts/run_all.sh
# Use built image to build python wheels
- task: build-wheels
image: coredeps-image
config:
inputs:
- name: deltachat-core-rust-release
path: .
outputs:
- name: py-wheels
path: ./python/.docker-tox/wheelhouse/
platform: linux
run:
path: bash
args:
- -exc
- |
scripts/run_all.sh
# Upload musl x86_64 wheels
- task: upload-wheels
config:
inputs:
- name: py-wheels
image_resource:
type: registry-image
source:
repository: debian
platform: linux
run:
path: sh
args:
- -ec
- |
apt-get update -y
apt-get install -y --no-install-recommends python3-pip python3-setuptools
pip3 install devpi
devpi use https://m.devpi.net/dc/master
devpi login ((devpi.login)) --password ((devpi.password))
devpi upload py-wheels/*musllinux_1_1_x86_64*
# Upload musl x86_64 wheels
- task: upload-wheels
config:
inputs:
- name: py-wheels
image_resource:
type: registry-image
source:
repository: debian
platform: linux
run:
path: sh
args:
- -ec
- |
apt-get update -y
apt-get install -y --no-install-recommends python3-pip python3-setuptools
pip3 install devpi
devpi use https://m.devpi.net/dc/master
devpi login ((devpi.login)) --password ((devpi.password))
devpi upload py-wheels/*musllinux_1_1_x86_64*
- name: python-musl-aarch64
plan:
- get: deltachat-core-rust
- get: deltachat-core-rust-release
trigger: true
- name: python-musl-aarch64
plan:
- get: deltachat-core-rust
- get: deltachat-core-rust-release
trigger: true
# Build manylinux image with additional dependencies
- task: build-coredeps
privileged: true
config:
inputs:
# Building the latest, not tagged coredeps
- name: deltachat-core-rust
image_resource:
source:
repository: concourse/oci-build-task
type: registry-image
outputs:
- name: coredeps-image
path: image
params:
CONTEXT: deltachat-core-rust/scripts/coredeps
UNPACK_ROOTFS: "true"
BUILD_ARG_BASEIMAGE: quay.io/pypa/musllinux_1_1_aarch64
platform: linux
caches:
- path: cache
run:
path: build
# Build manylinux image with additional dependencies
- task: build-coredeps
privileged: true
config:
inputs:
# Building the latest, not tagged coredeps
- name: deltachat-core-rust
image_resource:
source:
repository: concourse/oci-build-task
type: registry-image
outputs:
- name: coredeps-image
path: image
params:
CONTEXT: deltachat-core-rust/scripts/coredeps
UNPACK_ROOTFS: "true"
BUILD_ARG_BASEIMAGE: quay.io/pypa/musllinux_1_1_aarch64
platform: linux
caches:
- path: cache
run:
path: build
# Use built image to build python wheels
- task: build-wheels
image: coredeps-image
config:
inputs:
- name: deltachat-core-rust-release
path: .
outputs:
- name: py-wheels
path: ./python/.docker-tox/wheelhouse/
platform: linux
run:
path: bash
args:
- -exc
- |
scripts/run_all.sh
# Use built image to build python wheels
- task: build-wheels
image: coredeps-image
config:
inputs:
- name: deltachat-core-rust-release
path: .
outputs:
- name: py-wheels
path: ./python/.docker-tox/wheelhouse/
platform: linux
run:
path: bash
args:
- -exc
- |
scripts/run_all.sh
# Upload musl aarch64 wheels
- task: upload-wheels
config:
inputs:
- name: py-wheels
image_resource:
type: registry-image
source:
repository: debian
platform: linux
run:
path: sh
args:
- -ec
- |
apt-get update -y
apt-get install -y --no-install-recommends python3-pip python3-setuptools
pip3 install devpi
devpi use https://m.devpi.net/dc/master
devpi login ((devpi.login)) --password ((devpi.password))
devpi upload py-wheels/*musllinux_1_1_aarch64*
# Upload musl aarch64 wheels
- task: upload-wheels
config:
inputs:
- name: py-wheels
image_resource:
type: registry-image
source:
repository: debian
platform: linux
run:
path: sh
args:
- -ec
- |
apt-get update -y
apt-get install -y --no-install-recommends python3-pip python3-setuptools
pip3 install devpi
devpi use https://m.devpi.net/dc/master
devpi login ((devpi.login)) --password ((devpi.password))
devpi upload py-wheels/*musllinux_1_1_aarch64*
+4 -2
View File
@@ -115,8 +115,10 @@ def main():
print("after commit, on master make sure to: ")
print("")
print(f" git tag -a v{newversion}")
print(f" git push origin v{newversion}")
print(f" git tag -a {newversion}")
print(f" git push origin {newversion}")
print(f" git tag -a py-{newversion}")
print(f" git push origin py-{newversion}")
print("")
-22
View File
@@ -1,22 +0,0 @@
#!/usr/bin/env bash
# Updates provider database.
# Returns 1 if the database is changed, 0 otherwise.
set -euo pipefail
export TZ=UTC
# Provider database revision.
REV=3c8f7e846c915a183dc44536fb5480d1f25d7c42
CORE_ROOT="$PWD"
TMP="$(mktemp -d)"
git clone --filter=blob:none https://github.com/deltachat/provider-db.git "$TMP"
cd "$TMP"
git checkout "$REV"
DATE=$(git show -s --format=%cs)
"$CORE_ROOT"/scripts/create-provider-data-rs.py "$TMP/_providers" "$DATE" >"$CORE_ROOT/src/provider/data.rs"
rustfmt "$CORE_ROOT/src/provider/data.rs"
rm -fr "$TMP"
cd "$CORE_ROOT"
test -z "$(git status --porcelain src/provider/data.rs)"
+2 -2
View File
@@ -339,13 +339,13 @@ only on image changes.
In older specs, the profile-image was sent as an attachment
and `Chat-User-Avatar:` specified its name.
However, it turned out that these attachments are kind of unuexpected to users,
However, it turned out that these attachements are kind of unuexpected to users,
therefore the profile-image go to the header now.
# Locations
Locations can be attached to messages using
Locations can be attachted to messages using
[standard kml-files](https://www.opengeospatial.org/standards/kml/)
with well-known names.
+2 -5
View File
@@ -427,7 +427,7 @@ impl Config {
Ok(cfg)
}
/// Removes an existing account entirely.
/// Removes an existing acccount entirely.
pub async fn remove_account(&mut self, id: u32) -> Result<()> {
{
if let Some(idx) = self.inner.accounts.iter().position(|e| e.id == id) {
@@ -476,18 +476,15 @@ impl Config {
struct AccountConfig {
/// Unique id.
pub id: u32,
/// Root directory for all data for this account.
///
/// The path is relative to the account manager directory.
pub dir: std::path::PathBuf,
/// Universally unique account identifier.
pub uuid: Uuid,
}
impl AccountConfig {
/// Get the canonical dbfile name for this configuration.
/// Get the canoncial dbfile name for this configuration.
pub fn dbfile(&self, accounts_dir: &Path) -> std::path::PathBuf {
accounts_dir.join(&self.dir).join(DB_NAME)
}
+1 -1
View File
@@ -235,7 +235,7 @@ mod tests {
assert!(Aheader::from_str("foo").is_err());
assert!(Aheader::from_str("\n\n\n").is_err());
assert!(Aheader::from_str(" ;;").is_err());
assert!(Aheader::from_str("addr=a@t.de; unknown=1; keydata=jau").is_err());
assert!(Aheader::from_str("addr=a@t.de; unknwon=1; keydata=jau").is_err());
}
#[test]
+1 -3
View File
@@ -644,7 +644,6 @@ Authentication-Results: dkim=";
.unwrap();
}
#[ignore = "Disallowing keychanges is disabled for now"]
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_handle_authres_fails() -> Result<()> {
let mut tcm = TestContextManager::new();
@@ -822,8 +821,7 @@ Authentication-Results: dkim=";
.insert_str(0, "Authentication-Results: example.net; dkim=fail\n");
let rcvd = bob.recv_msg(&sent).await;
// Disallowing keychanges is disabled for now:
// assert!(rcvd.error.unwrap().contains("DKIM failed"));
assert!(rcvd.error.unwrap().contains("DKIM failed"));
// The message info should contain a warning:
assert!(message::get_msg_info(&bob, rcvd.id)
.await
+28 -22
View File
@@ -46,7 +46,7 @@ use crate::{location, sql};
pub enum ChatItem {
/// Chat message stored in the database.
Message {
/// Database ID of the message.
/// Database ID of the messsage.
msg_id: MsgId,
},
@@ -276,7 +276,7 @@ impl ChatId {
grpname,
grpid,
create_blocked,
create_smeared_timestamp(context),
create_smeared_timestamp(context).await,
create_protected,
param.unwrap_or_default(),
],
@@ -482,7 +482,7 @@ impl ChatId {
self,
&msg_text,
cmd,
create_smeared_timestamp(context),
create_smeared_timestamp(context).await,
None,
None,
None,
@@ -907,8 +907,7 @@ impl ChatId {
async fn parent_query<T, F>(self, context: &Context, fields: &str, f: F) -> Result<Option<T>>
where
F: Send + FnOnce(&rusqlite::Row) -> rusqlite::Result<T>,
T: Send + 'static,
F: FnOnce(&rusqlite::Row) -> rusqlite::Result<T>,
{
let sql = &context.sql;
let query = format!(
@@ -1206,10 +1205,7 @@ impl Chat {
}
}
Err(err) => {
error!(
context,
"failed to load contacts for {}: {:#}", chat.id, err
);
error!(context, "faild to load contacts for {}: {:#}", chat.id, err);
}
}
chat.name = chat_name;
@@ -1884,10 +1880,7 @@ pub(crate) async fn update_special_chat_names(context: &Context) -> Result<()> {
/// [`Deref`]: std::ops::Deref
#[derive(Debug)]
pub(crate) struct ChatIdBlocked {
/// Chat ID.
pub id: ChatId,
/// Whether the chat is blocked, unblocked or a contact request.
pub blocked: Blocked,
}
@@ -1959,6 +1952,7 @@ impl ChatIdBlocked {
_ => (),
}
let created_timestamp = create_smeared_timestamp(context).await;
let chat_id = context
.sql
.transaction(move |transaction| {
@@ -1971,7 +1965,7 @@ impl ChatIdBlocked {
chat_name,
params.to_string(),
create_blocked as u8,
create_smeared_timestamp(context)
created_timestamp,
],
)?;
let chat_id = ChatId::new(
@@ -2119,7 +2113,7 @@ async fn prepare_msg_common(
context,
msg,
update_msg_id,
create_smeared_timestamp(context),
create_smeared_timestamp(context).await,
)
.await?;
msg.chat_id = chat_id;
@@ -2150,7 +2144,7 @@ pub async fn is_contact_in_chat(
/// Sends a message object to a chat.
///
/// Sends the event #DC_EVENT_MSGS_CHANGED on success.
/// Sends the event #DC_EVENT_MSGS_CHANGED on succcess.
/// However, this does not imply, the message really reached the recipient -
/// 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.
@@ -2514,7 +2508,7 @@ pub async fn get_chat_msgs_ex(
context
.sql
.query_map(
// GLOB is used here instead of LIKE because it is case-sensitive
// GLOB is used here instead of LIKE becase it is case-sensitive
"SELECT m.id AS id, m.timestamp AS timestamp, m.param AS param, m.from_id AS from_id, m.to_id AS to_id
FROM msgs m
WHERE m.chat_id=?
@@ -2844,7 +2838,7 @@ pub async fn create_group_chat(
Chattype::Group,
chat_name,
grpid,
create_smeared_timestamp(context),
create_smeared_timestamp(context).await,
],
)
.await?;
@@ -2902,7 +2896,7 @@ pub async fn create_broadcast_list(context: &Context) -> Result<ChatId> {
Chattype::Broadcast,
chat_name,
grpid,
create_smeared_timestamp(context),
create_smeared_timestamp(context).await,
],
)
.await?;
@@ -3363,7 +3357,7 @@ pub async fn forward_msgs(context: &Context, msg_ids: &[MsgId], chat_id: ChatId)
if let Some(reason) = chat.why_cant_send(context).await? {
bail!("cannot send to {}: {}", chat_id, reason);
}
curr_timestamp = create_smeared_timestamps(context, msg_ids.len());
curr_timestamp = create_smeared_timestamps(context, msg_ids.len()).await;
let ids = context
.sql
.query_map(
@@ -3565,7 +3559,7 @@ pub async fn add_device_msg_with_importance(
msg.try_calc_and_set_dimensions(context).await.ok();
prepare_msg_blob(context, msg).await?;
let timestamp_sent = create_smeared_timestamp(context);
let timestamp_sent = create_smeared_timestamp(context).await;
// makes sure, the added message is the last one,
// even if the date is wrong (useful esp. when warning about bad dates)
@@ -3665,7 +3659,7 @@ pub async fn was_device_msg_ever_added(context: &Context, label: &str) -> Result
// - deletion in `msgs` with `ContactId::DEVICE` makes sure,
// no wrong information are shown in the device chat
// - deletion in `devmsglabels` makes sure,
// deleted messages are reset and useful messages can be added again
// deleted messages are resetted and useful messages can be added again
// - we reset the config-option `QuotaExceeding`
// that is used as a helper to drive the corresponding device message.
pub(crate) async fn delete_and_reset_all_device_msgs(context: &Context) -> Result<()> {
@@ -4093,6 +4087,7 @@ mod tests {
send_text_msg(&alice, alice_chat_id, "populate".to_string()).await?;
add_contact_to_chat(&alice, alice_chat_id, bob_id).await?;
tokio::time::sleep(std::time::Duration::from_millis(1100)).await;
let add1 = alice.pop_sent_msg().await;
add_contact_to_chat(&alice, alice_chat_id, claire_id).await?;
@@ -4111,18 +4106,29 @@ mod tests {
remove_contact_from_chat(&alice, alice_chat_id, daisy_id).await?;
let remove2 = alice.pop_sent_msg().await;
tokio::time::sleep(std::time::Duration::from_millis(1100)).await;
assert_eq!(get_chat_contacts(&alice, alice_chat_id).await?.len(), 2);
// Bob receives the add and deletion messages out of order
let bob = TestContext::new_bob().await;
bob.recv_msg(&add1).await;
tokio::time::sleep(std::time::Duration::from_millis(1100)).await;
bob.recv_msg(&add3).await;
tokio::time::sleep(std::time::Duration::from_millis(1100)).await;
let bob_chat_id = bob.recv_msg(&add2).await.chat_id;
tokio::time::sleep(std::time::Duration::from_millis(1100)).await;
assert_eq!(get_chat_contacts(&bob, bob_chat_id).await?.len(), 4);
bob.recv_msg(&remove2).await;
tokio::time::sleep(std::time::Duration::from_millis(1100)).await;
bob.recv_msg(&remove1).await;
tokio::time::sleep(std::time::Duration::from_millis(1100)).await;
assert_eq!(get_chat_contacts(&bob, bob_chat_id).await?.len(), 2);
Ok(())
@@ -4434,7 +4440,7 @@ mod tests {
.unwrap();
assert!(msg_id2.is_unset());
// ... unless everything is deleted and reset - as needed eg. on device switch
// ... unless everything is deleted and resetted - as needed eg. on device switch
delete_and_reset_all_device_msgs(&t).await.unwrap();
assert!(!was_device_msg_ever_added(&t, "some-label").await.unwrap());
let msg_id3 = add_device_msg(&t, Some("some-label"), Some(&mut msg))
+12 -30
View File
@@ -1,7 +1,5 @@
//! # Key-value configuration management.
use std::str::FromStr;
use anyhow::{ensure, Context as _, Result};
use strum::{EnumProperty, IntoEnumIterator};
use strum_macros::{AsRefStr, Display, EnumIter, EnumProperty, EnumString};
@@ -175,10 +173,6 @@ pub enum Config {
#[strum(props(default = "0"))]
DeleteDeviceAfter,
/// Move messages to the Trash folder instead of marking them "\Deleted". Overrides
/// `ProviderOptions::delete_to_trash`.
DeleteToTrash,
/// Save raw MIME messages with headers in the database if true.
SaveMimeHeaders,
@@ -233,9 +227,6 @@ pub enum Config {
/// Configured "Sent" folder.
ConfiguredSentboxFolder,
/// Configured "Trash" folder.
ConfiguredTrashFolder,
/// Unix timestamp of the last successful configuration.
ConfiguredTimestamp,
@@ -246,7 +237,7 @@ pub enum Config {
Configured,
/// All secondary self addresses separated by spaces
/// (`addr1@example.org addr2@example.org addr3@example.org`)
/// (`addr1@example.org addr2@exapmle.org addr3@example.org`)
SecondaryAddrs,
/// Read-only core version string.
@@ -300,9 +291,6 @@ pub enum Config {
/// See `crate::authres::update_authservid_candidates`.
AuthservIdCandidates,
/// Make all outgoing messages with Autocrypt header "multipart/signed".
SignUnencrypted,
/// Let the core save all events to the database.
/// This value is used internally to remember the MsgId of the logging xdc
#[strum(props(default = "0"))]
@@ -339,37 +327,30 @@ impl Context {
}
}
/// Returns Some(T) if a value for the given key exists and was successfully parsed.
/// Returns None if could not parse.
pub async fn get_config_parsed<T: FromStr>(&self, key: Config) -> Result<Option<T>> {
self.get_config(key)
.await
.map(|s: Option<String>| s.and_then(|s| s.parse().ok()))
}
/// Returns 32-bit signed integer configuration value for the given key.
pub async fn get_config_int(&self, key: Config) -> Result<i32> {
Ok(self.get_config_parsed(key).await?.unwrap_or_default())
self.get_config(key)
.await
.map(|s: Option<String>| s.and_then(|s| s.parse().ok()).unwrap_or_default())
}
/// Returns 64-bit signed integer configuration value for the given key.
pub async fn get_config_i64(&self, key: Config) -> Result<i64> {
Ok(self.get_config_parsed(key).await?.unwrap_or_default())
self.get_config(key)
.await
.map(|s: Option<String>| s.and_then(|s| s.parse().ok()).unwrap_or_default())
}
/// Returns 64-bit unsigned integer configuration value for the given key.
pub async fn get_config_u64(&self, key: Config) -> Result<u64> {
Ok(self.get_config_parsed(key).await?.unwrap_or_default())
}
/// Returns boolean configuration value (if any) for the given key.
pub async fn get_config_bool_opt(&self, key: Config) -> Result<Option<bool>> {
Ok(self.get_config_parsed::<i32>(key).await?.map(|x| x != 0))
self.get_config(key)
.await
.map(|s: Option<String>| s.and_then(|s| s.parse().ok()).unwrap_or_default())
}
/// Returns boolean configuration value for the given key.
pub async fn get_config_bool(&self, key: Config) -> Result<bool> {
Ok(self.get_config_bool_opt(key).await?.unwrap_or_default())
Ok(self.get_config_int(key).await? != 0)
}
/// Returns true if movebox ("DeltaChat" folder) should be watched.
@@ -569,6 +550,7 @@ fn get_config_keys_string() -> String {
#[cfg(test)]
mod tests {
use std::str::FromStr;
use std::string::ToString;
use num_traits::FromPrimitive;
+2 -6
View File
@@ -249,7 +249,7 @@ async fn configure(ctx: &Context, param: &mut LoginParam) -> Result<()> {
}
}
},
strict_tls: Some(provider.opt.strict_tls),
strict_tls: Some(provider.strict_tls),
})
.collect();
@@ -338,7 +338,7 @@ async fn configure(ctx: &Context, param: &mut LoginParam) -> Result<()> {
.collect();
let provider_strict_tls = param
.provider
.map_or(socks5_config.is_some(), |provider| provider.opt.strict_tls);
.map_or(socks5_config.is_some(), |provider| provider.strict_tls);
let smtp_config_task = task::spawn(async move {
let mut smtp_configured = false;
@@ -646,14 +646,10 @@ async fn try_smtp_one_param(
}
}
/// Failure to connect and login with email client configuration.
#[derive(Debug, thiserror::Error)]
#[error("Trying {config}…\nError: {msg}")]
pub struct ConfigurationError {
/// Tried configuration description.
config: String,
/// Error message.
msg: String,
}
+1 -1
View File
@@ -4,7 +4,7 @@ use crate::provider::{Protocol, Socket};
/// Set of variable parameters to try during configuration.
///
/// Can be loaded from offline provider database, online configuration
/// Can be loaded from offline provider database, online configuraiton
/// or derived from user entered parameters.
#[derive(Debug, Clone, PartialEq)]
pub(crate) struct ServerParams {
+5 -5
View File
@@ -103,7 +103,7 @@ pub(crate) const DC_RESEND_USER_AVATAR_DAYS: i64 = 14;
// warn about an outdated app after a given number of days.
// as we use the "provider-db generation date" as reference (that might not be updated very often)
// and as not all system get speedy updates,
// do not use too small value that will annoy users checking for nonexistent updates.
// do not use too small value that will annoy users checking for nonexistant updates.
pub(crate) const DC_OUTDATED_WARNING_DAYS: i64 = 365;
/// messages that should be deleted get this chat_id; the messages are deleted from the working thread later then. This is also needed as rfc724_mid should be preset as long as the message is not deleted on the server (otherwise it is downloaded again)
@@ -190,18 +190,18 @@ pub const DC_LP_AUTH_NORMAL: i32 = 0x4;
pub const DC_LP_AUTH_FLAGS: i32 = DC_LP_AUTH_OAUTH2 | DC_LP_AUTH_NORMAL;
/// How many existing messages shall be fetched after configuration.
pub(crate) const DC_FETCH_EXISTING_MSGS_COUNT: i64 = 100;
pub const DC_FETCH_EXISTING_MSGS_COUNT: i64 = 100;
// max. width/height of an avatar
pub(crate) const BALANCED_AVATAR_SIZE: u32 = 256;
pub(crate) const WORSE_AVATAR_SIZE: u32 = 128;
pub const BALANCED_AVATAR_SIZE: u32 = 256;
pub const WORSE_AVATAR_SIZE: u32 = 128;
// max. width/height of images
pub const BALANCED_IMAGE_SIZE: u32 = 1280;
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(crate) const DC_FOLDERS_CONFIGURED_VERSION: i32 = 4;
pub(crate) const DC_FOLDERS_CONFIGURED_VERSION: i32 = 3;
#[cfg(test)]
mod tests {
+38 -36
View File
@@ -869,45 +869,47 @@ impl Contact {
Ok(ret)
}
/// Adds blocked mailinglists as contacts
/// to allow unblocking them as if they are contacts
/// (this way, only one unblock-ffi is needed and only one set of ui-functions,
/// from the users perspective,
/// there is not much difference in an email- and a mailinglist-address)
// add blocked mailinglists as contacts
// to allow unblocking them as if they are contacts
// (this way, only one unblock-ffi is needed and only one set of ui-functions,
// from the users perspective,
// there is not much difference in an email- and a mailinglist-address)
async fn update_blocked_mailinglist_contacts(context: &Context) -> Result<()> {
context
let blocked_mailinglists = context
.sql
.transaction(move |transaction| {
let mut stmt = transaction
.prepare("SELECT name, grpid FROM chats WHERE type=? AND blocked=?")?;
let rows = stmt.query_map(params![Chattype::Mailinglist, Blocked::Yes], |row| {
let name: String = row.get(0)?;
let grpid: String = row.get(1)?;
Ok((name, grpid))
})?;
let blocked_mailinglists = rows.collect::<std::result::Result<Vec<_>, _>>()?;
for (name, grpid) in blocked_mailinglists {
let count = transaction.query_row(
"SELECT COUNT(id) FROM contacts WHERE addr=?",
[&grpid],
|row| {
let count: isize = row.get(0)?;
Ok(count)
},
)?;
if count == 0 {
transaction.execute("INSERT INTO contacts (addr) VALUES (?)", [&grpid])?;
}
// Always do an update in case the blocking is reset or name is changed.
transaction.execute(
"UPDATE contacts SET name=?, origin=?, blocked=1 WHERE addr=?",
params![&name, Origin::MailinglistAddress, &grpid],
)?;
}
Ok(())
})
.query_map(
"SELECT name, grpid FROM chats WHERE type=? AND blocked=?;",
paramsv![Chattype::Mailinglist, Blocked::Yes],
|row| Ok((row.get::<_, String>(0)?, row.get::<_, String>(1)?)),
|rows| {
rows.collect::<std::result::Result<Vec<_>, _>>()
.map_err(Into::into)
},
)
.await?;
for (name, grpid) in blocked_mailinglists {
if !context
.sql
.exists(
"SELECT COUNT(id) FROM contacts WHERE addr=?;",
paramsv![grpid],
)
.await?
{
context
.sql
.execute("INSERT INTO contacts (addr) VALUES (?);", paramsv![grpid])
.await?;
}
// always do an update in case the blocking is reset or name is changed
context
.sql
.execute(
"UPDATE contacts SET name=?, origin=?, blocked=1 WHERE addr=?;",
paramsv![name, Origin::MailinglistAddress, grpid],
)
.await?;
}
Ok(())
}
+7 -61
View File
@@ -4,11 +4,10 @@ use std::collections::{BTreeMap, HashMap};
use std::ffi::OsString;
use std::ops::Deref;
use std::path::{Path, PathBuf};
use std::sync::atomic::AtomicBool;
use std::sync::Arc;
use std::time::{Duration, Instant, SystemTime};
use anyhow::{bail, ensure, Context as _, Result};
use anyhow::{bail, ensure, Result};
use async_channel::{self as channel, Receiver, Sender};
use ratelimit::Ratelimit;
use tokio::sync::{Mutex, RwLock};
@@ -27,7 +26,6 @@ use crate::quota::QuotaInfo;
use crate::scheduler::Scheduler;
use crate::sql::Sql;
use crate::stock_str::StockStrings;
use crate::timesmearing::SmearedTimestamp;
use crate::tools::{duration_to_str, time};
/// Builder for the [`Context`].
@@ -114,7 +112,7 @@ impl ContextBuilder {
/// Sets the event channel for this [`Context`].
///
/// Mostly useful when using multiple [`Context`]s, this allows creating one [`Events`]
/// channel and passing it to all [`Context`]s so all events are received on the same
/// channel and passing it to all [`Context`]s so all events are recieved on the same
/// channel.
///
/// Note that the [account manager](crate::accounts::Accounts) is designed to handle the
@@ -190,13 +188,13 @@ pub struct InnerContext {
/// Blob directory path
pub(crate) blobdir: PathBuf,
pub(crate) sql: Sql,
pub(crate) smeared_timestamp: SmearedTimestamp,
pub(crate) last_smeared_timestamp: RwLock<i64>,
running_state: RwLock<RunningState>,
/// Mutex to avoid generating the key for the user more than once.
pub(crate) generating_key_mutex: Mutex<()>,
/// Mutex to enforce only a single running oauth2 is running.
pub(crate) oauth2_mutex: Mutex<()>,
/// Mutex to prevent a race condition when a "your pw is wrong" warning is sent, resulting in multiple messages being sent.
/// Mutex to prevent a race condition when a "your pw is wrong" warning is sent, resulting in multiple messeges being sent.
pub(crate) wrong_pw_warning_mutex: Mutex<()>,
pub(crate) translated_stockstrings: StockStrings,
pub(crate) events: Events,
@@ -208,12 +206,6 @@ pub struct InnerContext {
/// Set to `None` if quota was never tried to load.
pub(crate) quota: RwLock<Option<QuotaInfo>>,
/// Set to true if quota update is requested.
pub(crate) quota_update_request: AtomicBool,
/// IMAP UID resync request.
pub(crate) resync_request: AtomicBool,
/// Server ID response if ID capability is supported
/// and the server returned non-NIL on the inbox connection.
/// <https://datatracker.ietf.org/doc/html/rfc2971>
@@ -234,7 +226,7 @@ pub struct InnerContext {
/// `last_error` should be used to avoid races with the event thread.
pub(crate) last_error: std::sync::RwLock<String>,
/// If debug logging is enabled, this contains all necessary information
/// If debug logging is enabled, this contains all neccesary information
pub(crate) debug_logging: RwLock<Option<DebugLogging>>,
}
@@ -242,7 +234,7 @@ pub struct InnerContext {
pub(crate) struct DebugLogging {
/// The message containing the logging xdc
pub(crate) msg_id: MsgId,
/// Handle to the background task responsible for sending
/// Handle to the background task responisble for sending
pub(crate) loop_handle: task::JoinHandle<()>,
/// Channel that log events should be send to
/// A background loop will receive and handle them
@@ -364,7 +356,7 @@ impl Context {
blobdir,
running_state: RwLock::new(Default::default()),
sql: Sql::new(dbfile),
smeared_timestamp: SmearedTimestamp::new(),
last_smeared_timestamp: RwLock::new(0),
generating_key_mutex: Mutex::new(()),
oauth2_mutex: Mutex::new(()),
wrong_pw_warning_mutex: Mutex::new(()),
@@ -373,8 +365,6 @@ impl Context {
scheduler: RwLock::new(None),
ratelimit: RwLock::new(Ratelimit::new(Duration::new(60, 0), 6.0)), // Allow to send 6 messages immediately, no more than once every 10 seconds.
quota: RwLock::new(None),
quota_update_request: AtomicBool::new(false),
resync_request: AtomicBool::new(false),
server_id: RwLock::new(None),
creation_time: std::time::SystemTime::now(),
last_full_folder_scan: Mutex::new(None),
@@ -633,10 +623,6 @@ impl Context {
.get_config(Config::ConfiguredMvboxFolder)
.await?
.unwrap_or_else(|| "<unset>".to_string());
let configured_trash_folder = self
.get_config(Config::ConfiguredTrashFolder)
.await?
.unwrap_or_else(|| "<unset>".to_string());
let mut res = get_info();
@@ -703,7 +689,6 @@ impl Context {
res.insert("configured_inbox_folder", configured_inbox_folder);
res.insert("configured_sentbox_folder", configured_sentbox_folder);
res.insert("configured_mvbox_folder", configured_mvbox_folder);
res.insert("configured_trash_folder", configured_trash_folder);
res.insert("mdns_enabled", mdns_enabled.to_string());
res.insert("e2ee_enabled", e2ee_enabled.to_string());
res.insert(
@@ -737,12 +722,6 @@ impl Context {
.await?
.to_string(),
);
res.insert(
"delete_to_trash",
self.get_config(Config::DeleteToTrash)
.await?
.unwrap_or_else(|| "<unset>".to_string()),
);
res.insert(
"last_housekeeping",
self.get_config_int(Config::LastHousekeeping)
@@ -767,12 +746,6 @@ impl Context {
.await?
.unwrap_or_default(),
);
res.insert(
"sign_unencrypted",
self.get_config_int(Config::SignUnencrypted)
.await?
.to_string(),
);
res.insert(
"debug_logging",
@@ -914,33 +887,6 @@ impl Context {
Ok(mvbox.as_deref() == Some(folder_name))
}
/// Returns true if given folder name is the name of the trash folder.
pub async fn is_trash(&self, folder_name: &str) -> Result<bool> {
let trash = self.get_config(Config::ConfiguredTrashFolder).await?;
Ok(trash.as_deref() == Some(folder_name))
}
pub(crate) async fn should_delete_to_trash(&self) -> Result<bool> {
if let Some(v) = self.get_config_bool_opt(Config::DeleteToTrash).await? {
return Ok(v);
}
if let Some(provider) = self.get_configured_provider().await? {
return Ok(provider.opt.delete_to_trash);
}
Ok(false)
}
/// Returns `target` for deleted messages as per `imap` table. Empty string means "delete w/o
/// moving to trash".
pub(crate) async fn get_delete_msgs_target(&self) -> Result<String> {
if !self.should_delete_to_trash().await? {
return Ok("".into());
}
self.get_config(Config::ConfiguredTrashFolder)
.await?
.context("No configured trash folder")
}
pub(crate) fn derive_blobdir(dbfile: &Path) -> PathBuf {
let mut blob_fname = OsString::new();
blob_fname.push(dbfile.file_name().unwrap_or_default());
+1 -2
View File
@@ -99,8 +99,7 @@ pub(crate) async fn prepare_decryption(
from,
autocrypt_header.as_ref(),
message_time,
// Disallowing keychanges is disabled for now:
true, // dkim_results.allow_keychange,
dkim_results.allow_keychange,
)
.await?;
+8 -3
View File
@@ -13,6 +13,7 @@ use crate::imap::{Imap, ImapActionResult};
use crate::job::{self, Action, Job, Status};
use crate::message::{Message, MsgId, Viewtype};
use crate::mimeparser::{MimeMessage, Part};
use crate::param::Params;
use crate::tools::time;
use crate::{job_try, stock_str, EventType};
@@ -22,7 +23,7 @@ use crate::{job_try, stock_str, EventType};
/// need to be downloaded completely to handle them correctly,
/// eg. to assign them to the correct chat.
/// As these messages are typically small,
/// they're caught by `MIN_DOWNLOAD_LIMIT`.
/// they're catched by `MIN_DOWNLOAD_LIMIT`.
const MIN_DOWNLOAD_LIMIT: u32 = 32768;
/// If a message is downloaded only partially
@@ -85,7 +86,11 @@ impl MsgId {
DownloadState::Available | DownloadState::Failure => {
self.update_download_state(context, DownloadState::InProgress)
.await?;
job::add(context, Job::new(Action::DownloadMsg, self.to_u32())).await?;
job::add(
context,
Job::new(Action::DownloadMsg, self.to_u32(), Params::new(), 0),
)
.await?;
}
}
Ok(())
@@ -133,7 +138,7 @@ impl Job {
context
.sql
.query_row_optional(
"SELECT uid, folder FROM imap WHERE rfc724_mid=? AND target=folder",
"SELECT uid, folder FROM imap WHERE rfc724_mid=? AND target!=''",
paramsv![msg.rfc724_mid],
|row| {
let server_uid: u32 = row.get(0)?;
-13
View File
@@ -124,19 +124,6 @@ impl EncryptHelper {
Ok(ctext)
}
/// Signs the passed-in `mail` using the private key from `context`.
/// Returns the payload and the signature.
pub async fn sign(
self,
context: &Context,
mail: lettre_email::PartBuilder,
) -> Result<(lettre_email::MimeMessage, String)> {
let sign_key = SignedSecretKey::load_self(context).await?;
let mime_message = mail.build();
let signature = pgp::pk_calc_signature(mime_message.as_string().as_bytes(), &sign_key)?;
Ok((mime_message, signature))
}
}
/// Ensures a private key exists for the configured user.
+4 -10
View File
@@ -588,25 +588,19 @@ pub(crate) async fn delete_expired_imap_messages(context: &Context) -> Result<()
now - max(delete_server_after, MIN_DELETE_SERVER_AFTER),
),
};
let target = context.get_delete_msgs_target().await?;
context
.sql
.execute(
"UPDATE imap
SET target=?
SET target=''
WHERE rfc724_mid IN (
SELECT rfc724_mid FROM msgs
WHERE ((download_state = 0 AND timestamp < ?) OR
(download_state != 0 AND timestamp < ?) OR
(ephemeral_timestamp != 0 AND ephemeral_timestamp <= ?))
)",
paramsv![
target,
threshold_timestamp,
threshold_timestamp_extended,
now,
],
paramsv![threshold_timestamp, threshold_timestamp_extended, now],
)
.await?;
@@ -650,7 +644,7 @@ mod tests {
use crate::download::DownloadState;
use crate::receive_imf::receive_imf;
use crate::test_utils::TestContext;
use crate::timesmearing::MAX_SECONDS_TO_LEND_FROM_FUTURE;
use crate::tools::MAX_SECONDS_TO_LEND_FROM_FUTURE;
use crate::{
chat::{self, create_group_chat, send_text_msg, Chat, ChatItem, ProtectionStatus},
tools::IsNoneOrEmpty,
@@ -1273,7 +1267,7 @@ mod tests {
// protection.
//
// Previously Delta Chat fallen back to using <first@example.com> in this case and
// compared received timer value to the timer value of the <first@example.com>. Because
// compared received timer value to the timer value of the <first@examle.com>. Because
// their timer values are the same ("disabled"), Delta Chat assumed that the timer was not
// changed explicitly and the change should be ignored.
//
+2 -2
View File
@@ -70,7 +70,7 @@ impl Events {
pub struct EventEmitter(Receiver<Event>);
impl EventEmitter {
/// Async recv of an event. Return `None` if the `Sender` has been dropped.
/// Async recv of an event. Return `None` if the `Sender` has been droped.
pub async fn recv(&self) -> Option<Event> {
self.0.recv().await.ok()
}
@@ -89,7 +89,7 @@ impl futures::stream::Stream for EventEmitter {
/// The event emitted by a [`Context`] from an [`EventEmitter`].
///
/// Events are documented on the C/FFI API in `deltachat.h` as `DC_EVENT_*` constants. The
/// Events are documented on the C/FFI API in `deltachat.h` as `DC_EVENT_*` contants. The
/// context emits them in relation to various operations happening, a lot of these are again
/// documented in `deltachat.h`.
///
+1 -1
View File
@@ -5,7 +5,7 @@
/// Fuzzing target for simplify().
///
/// Calls simplify() and panics if simplify() panics.
/// Does not return any value to avoid exposing internal crate types.
/// Does not return any vaule to avoid exposing internal crate types.
#[cfg(fuzzing)]
pub fn simplify(input: String, is_chat_message: bool) {
crate::simplify::simplify(input, is_chat_message);
+91 -162
View File
@@ -113,17 +113,13 @@ impl async_imap::Authenticator for OAuth2 {
}
}
#[derive(Debug, Display, PartialEq, Eq, Clone, Copy)]
pub enum FolderMeaning {
#[derive(Debug, PartialEq, Clone, Copy)]
enum FolderMeaning {
Unknown,
/// Spam folder.
Spam,
Inbox,
Mvbox,
Sent,
Trash,
Drafts,
Other,
/// Virtual folders.
///
@@ -135,15 +131,13 @@ pub enum FolderMeaning {
}
impl FolderMeaning {
pub fn to_config(self) -> Option<Config> {
fn to_config(self) -> Option<Config> {
match self {
FolderMeaning::Unknown => None,
FolderMeaning::Spam => None,
FolderMeaning::Inbox => Some(Config::ConfiguredInboxFolder),
FolderMeaning::Mvbox => Some(Config::ConfiguredMvboxFolder),
FolderMeaning::Sent => Some(Config::ConfiguredSentboxFolder),
FolderMeaning::Trash => Some(Config::ConfiguredTrashFolder),
FolderMeaning::Drafts => None,
FolderMeaning::Other => None,
FolderMeaning::Virtual => None,
}
}
@@ -151,11 +145,8 @@ impl FolderMeaning {
#[derive(Debug)]
struct ImapConfig {
/// Email address.
pub addr: String,
pub lp: ServerLoginParam,
/// SOCKS 5 configuration.
pub socks5_config: Option<Socks5Config>,
pub strict_tls: bool,
}
@@ -279,7 +270,7 @@ impl Imap {
param
.provider
.map_or(param.socks5_config.is_some(), |provider| {
provider.opt.strict_tls
provider.strict_tls
}),
idle_interrupt_receiver,
)?;
@@ -458,7 +449,7 @@ impl Imap {
&mut self,
context: &Context,
watch_folder: &str,
folder_meaning: FolderMeaning,
is_spam_folder: bool,
) -> Result<()> {
if !context.sql.is_open().await {
// probably shutdown
@@ -467,7 +458,7 @@ impl Imap {
self.prepare(context).await?;
let msgs_fetched = self
.fetch_new_messages(context, watch_folder, folder_meaning, false)
.fetch_new_messages(context, watch_folder, is_spam_folder, false)
.await
.context("fetch_new_messages")?;
if msgs_fetched && context.get_config_delete_device_after().await?.is_some() {
@@ -499,60 +490,49 @@ impl Imap {
pub(crate) async fn resync_folder_uids(
&mut self,
context: &Context,
folder: &str,
folder_meaning: FolderMeaning,
folder: String,
) -> Result<()> {
// Collect pairs of UID and Message-ID.
let mut msgs = BTreeMap::new();
let mut msg_ids = BTreeMap::new();
let session = self
.session
.as_mut()
.context("IMAP No connection established")?;
session.select_folder(context, Some(folder)).await?;
session.select_folder(context, Some(&folder)).await?;
let mut list = session
.uid_fetch("1:*", RFC724MID_UID)
.await
.with_context(|| format!("can't resync folder {folder}"))?;
while let Some(fetch) = list.next().await {
let fetch = fetch?;
let headers = match get_fetch_headers(&fetch) {
Ok(headers) => headers,
Err(err) => {
warn!(context, "Failed to parse FETCH headers: {}", err);
continue;
}
};
let message_id = prefetch_get_message_id(&headers);
let msg = fetch?;
if let (Some(uid), Some(rfc724_mid)) = (fetch.uid, message_id) {
msgs.insert(
uid,
(
rfc724_mid,
target_folder(context, folder, folder_meaning, &headers).await?,
),
);
// Get Message-ID
let message_id =
get_fetch_headers(&msg).map_or(None, |headers| prefetch_get_message_id(&headers));
if let (Some(uid), Some(rfc724_mid)) = (msg.uid, message_id) {
msg_ids.insert(uid, rfc724_mid);
}
}
info!(
context,
"Resync: collected {} message IDs in folder {}",
msgs.len(),
folder,
msg_ids.len(),
&folder
);
let uid_validity = get_uidvalidity(context, folder).await?;
let uid_validity = get_uidvalidity(context, &folder).await?;
// Write collected UIDs to SQLite database.
context
.sql
.transaction(move |transaction| {
transaction.execute("DELETE FROM imap WHERE folder=?", params![folder])?;
for (uid, (rfc724_mid, target)) in &msgs {
for (uid, rfc724_mid) in &msg_ids {
// This may detect previously undetected moved
// messages, so we update server_folder too.
transaction.execute(
@@ -561,7 +541,7 @@ impl Imap {
ON CONFLICT(folder, uid, uidvalidity)
DO UPDATE SET rfc724_mid=excluded.rfc724_mid,
target=excluded.target",
params![rfc724_mid, folder, uid, uid_validity, target],
params![rfc724_mid, folder, uid, uid_validity, folder],
)?;
}
Ok(())
@@ -703,10 +683,10 @@ impl Imap {
&mut self,
context: &Context,
folder: &str,
folder_meaning: FolderMeaning,
is_spam_folder: bool,
fetch_existing_msgs: bool,
) -> Result<bool> {
if should_ignore_folder(context, folder, folder_meaning).await? {
if should_ignore_folder(context, folder, is_spam_folder).await? {
info!(context, "Not fetching from {}", folder);
return Ok(false);
}
@@ -733,6 +713,8 @@ impl Imap {
};
let read_cnt = msgs.len();
let show_emails = ShowEmails::from_i32(context.get_config_int(Config::ShowEmails).await?)
.unwrap_or_default();
let download_limit = context.download_limit().await?;
let mut uids_fetch = Vec::<(_, bool /* partially? */)>::with_capacity(msgs.len() + 1);
let mut uid_message_ids = BTreeMap::new();
@@ -750,7 +732,14 @@ impl Imap {
// Get the Message-ID or generate a fake one to identify the message in the database.
let message_id = prefetch_get_or_create_message_id(&headers);
let target = target_folder(context, folder, folder_meaning, &headers).await?;
let target = match target_folder(context, folder, is_spam_folder, &headers).await? {
Some(config) => match context.get_config(config).await? {
Some(target) => target,
None => folder.to_string(),
},
None => folder.to_string(),
};
context
.sql
@@ -774,13 +763,14 @@ impl Imap {
// Never download messages directly from the spam folder.
// If the sender is known, the message will be moved to the Inbox or Mvbox
// and then we download the message from there.
// Also see `spam_target_folder_cfg()`.
&& folder_meaning != FolderMeaning::Spam
// Also see `spam_target_folder()`.
&& !is_spam_folder
&& prefetch_should_download(
context,
&headers,
&message_id,
fetch_response.flags(),
show_emails,
)
.await.context("prefetch_should_download")?
{
@@ -880,21 +870,17 @@ impl Imap {
.context("failed to get recipients from the inbox")?;
if context.get_config_bool(Config::FetchExistingMsgs).await? {
for meaning in [
FolderMeaning::Mvbox,
FolderMeaning::Inbox,
FolderMeaning::Sent,
for config in &[
Config::ConfiguredMvboxFolder,
Config::ConfiguredInboxFolder,
Config::ConfiguredSentboxFolder,
] {
let config = match meaning.to_config() {
Some(c) => c,
None => continue,
};
if let Some(folder) = context.get_config(config).await? {
if let Some(folder) = context.get_config(*config).await? {
info!(
context,
"Fetching existing messages from folder \"{}\"", folder
);
self.fetch_new_messages(context, &folder, meaning, true)
self.fetch_new_messages(context, &folder, false, true)
.await
.context("could not fetch existing messages")?;
}
@@ -904,24 +890,6 @@ impl Imap {
info!(context, "Done fetching existing messages.");
Ok(())
}
/// Synchronizes UIDs for all folders.
pub(crate) async fn resync_folders(&mut self, context: &Context) -> Result<()> {
self.prepare(context).await?;
let all_folders = self
.list_folders(context)
.await
.context("listing folders for resync")?;
for folder in all_folders {
let folder_meaning = get_folder_meaning(&folder);
if folder_meaning != FolderMeaning::Virtual {
self.resync_folder_uids(context, folder.name(), folder_meaning)
.await?;
}
}
Ok(())
}
}
impl Session {
@@ -984,60 +952,44 @@ impl Session {
return Ok(());
}
Err(err) => {
if context.should_delete_to_trash().await? {
error!(
context,
"Cannot move messages {} to {}, no fallback to COPY/DELETE because \
delete_to_trash is set. Error: {:#}",
set,
target,
err,
);
return Err(err.into());
}
warn!(
context,
"Cannot move messages, fallback to COPY/DELETE {} to {}: {}",
"Cannot move message, fallback to COPY/DELETE {} to {}: {}",
set,
target,
err
);
}
}
}
// Server does not support MOVE or MOVE failed.
// Copy messages to the destination folder if needed and mark records for deletion.
let copy = !context.is_trash(target).await?;
if copy {
} else {
info!(
context,
"Server does not support MOVE, fallback to COPY/DELETE {} to {}", set, target
);
self.uid_copy(&set, &target).await?;
} else {
error!(
context,
"Server does not support MOVE, fallback to DELETE {} to {}", set, target,
);
}
context
.sql
.execute(
&format!(
"UPDATE imap SET target='' WHERE id IN ({})",
sql::repeat_vars(row_ids.len())
),
rusqlite::params_from_iter(row_ids),
)
.await
.context("cannot plan deletion of messages")?;
if copy {
context.emit_event(EventType::ImapMessageMoved(format!(
"IMAP messages {set} copied to {target}"
)));
// Server does not support MOVE or MOVE failed.
// Copy the message to the destination folder and mark the record for deletion.
match self.uid_copy(&set, &target).await {
Ok(()) => {
context
.sql
.execute(
&format!(
"UPDATE imap SET target='' WHERE id IN ({})",
sql::repeat_vars(row_ids.len())
),
rusqlite::params_from_iter(row_ids),
)
.await
.context("cannot plan deletion of copied messages")?;
context.emit_event(EventType::ImapMessageMoved(format!(
"IMAP messages {set} copied to {target}"
)));
Ok(())
}
Err(err) => Err(err.into()),
}
Ok(())
}
/// Moves and deletes messages as planned in the `imap` table.
@@ -1394,7 +1346,7 @@ impl Imap {
// Check if FETCH response is already in `uid_msgs`.
let mut fetch_response = uid_msgs.remove(&request_uid);
// Try to find a requested UID in returned FETCH responses.
// Try to find a requsted UID in returned FETCH responses.
while fetch_response.is_none() {
let next_fetch_response =
if let Some(next_fetch_response) = fetch_responses.next().await {
@@ -1692,7 +1644,7 @@ impl Imap {
}
}
let folder_meaning = get_folder_meaning_by_attrs(folder.attributes());
let folder_meaning = get_folder_meaning(&folder);
let folder_name_meaning = get_folder_meaning_by_name(folder.name());
if let Some(config) = folder_meaning.to_config() {
// Always takes precedence
@@ -1824,7 +1776,7 @@ async fn should_move_out_of_spam(
/// If this returns None, the message will not be moved out of the
/// Spam folder, and as `fetch_new_messages()` doesn't download
/// messages from the Spam folder, the message will be ignored.
async fn spam_target_folder_cfg(
async fn spam_target_folder(
context: &Context,
headers: &[mailparse::MailHeader<'_>],
) -> Result<Option<Config>> {
@@ -1845,18 +1797,18 @@ async fn spam_target_folder_cfg(
/// Returns `ConfiguredInboxFolder`, `ConfiguredMvboxFolder` or `ConfiguredSentboxFolder` if
/// the message needs to be moved from `folder`. Otherwise returns `None`.
pub async fn target_folder_cfg(
pub async fn target_folder(
context: &Context,
folder: &str,
folder_meaning: FolderMeaning,
is_spam_folder: bool,
headers: &[mailparse::MailHeader<'_>],
) -> Result<Option<Config>> {
if context.is_mvbox(folder).await? {
return Ok(None);
}
if folder_meaning == FolderMeaning::Spam {
spam_target_folder_cfg(context, headers).await
if is_spam_folder {
spam_target_folder(context, headers).await
} else if needs_move_to_mvbox(context, headers).await? {
Ok(Some(Config::ConfiguredMvboxFolder))
} else {
@@ -1864,21 +1816,6 @@ pub async fn target_folder_cfg(
}
}
pub async fn target_folder(
context: &Context,
folder: &str,
folder_meaning: FolderMeaning,
headers: &[mailparse::MailHeader<'_>],
) -> Result<String> {
match target_folder_cfg(context, folder, folder_meaning, headers).await? {
Some(config) => match context.get_config(config).await? {
Some(target) => Ok(target),
None => Ok(folder.to_string()),
},
None => Ok(folder.to_string()),
}
}
async fn needs_move_to_mvbox(
context: &Context,
headers: &[mailparse::MailHeader<'_>],
@@ -2003,10 +1940,10 @@ fn get_folder_meaning_by_name(folder_name: &str) -> FolderMeaning {
}
}
fn get_folder_meaning_by_attrs(folder_attrs: &[NameAttribute]) -> FolderMeaning {
for attr in folder_attrs {
fn get_folder_meaning(folder_name: &Name) -> FolderMeaning {
for attr in folder_name.attributes() {
match attr {
NameAttribute::Trash => return FolderMeaning::Trash,
NameAttribute::Trash => return FolderMeaning::Other,
NameAttribute::Sent => return FolderMeaning::Sent,
NameAttribute::Junk => return FolderMeaning::Spam,
NameAttribute::Drafts => return FolderMeaning::Drafts,
@@ -2024,13 +1961,6 @@ fn get_folder_meaning_by_attrs(folder_attrs: &[NameAttribute]) -> FolderMeaning
FolderMeaning::Unknown
}
pub(crate) fn get_folder_meaning(folder: &Name) -> FolderMeaning {
match get_folder_meaning_by_attrs(folder.attributes()) {
FolderMeaning::Unknown => get_folder_meaning_by_name(folder.name()),
meaning => meaning,
}
}
/// Parses the headers from the FETCH result.
fn get_fetch_headers(prefetch_msg: &Fetch) -> Result<Vec<mailparse::MailHeader>> {
match prefetch_msg.header() {
@@ -2075,6 +2005,7 @@ pub(crate) async fn prefetch_should_download(
headers: &[mailparse::MailHeader<'_>],
message_id: &str,
mut flags: impl Iterator<Item = Flag<'_>>,
show_emails: ShowEmails,
) -> Result<bool> {
if message::rfc724_mid_exists(context, message_id)
.await?
@@ -2134,9 +2065,6 @@ pub(crate) async fn prefetch_should_download(
})
.unwrap_or_default();
let show_emails =
ShowEmails::from_i32(context.get_config_int(Config::ShowEmails).await?).unwrap_or_default();
let show = is_autocrypt_setup_message
|| match show_emails {
ShowEmails::Off => is_chat_message || is_reply_to_chat_message,
@@ -2204,11 +2132,11 @@ async fn mark_seen_by_uid(
.with_context(|| format!("failed to start ephemeral timer for message {msg_id}"))?;
Ok(Some(chat_id))
} else {
// Message state has not changed.
// Message state has not chnaged.
Ok(None)
}
} else {
// There is no message is `msgs` table matching the given UID.
// There is no message is `msgs` table matchng the given UID.
Ok(None)
}
}
@@ -2344,7 +2272,7 @@ pub async fn get_config_last_seen_uid(context: &Context, folder: &str) -> Result
async fn should_ignore_folder(
context: &Context,
folder: &str,
folder_meaning: FolderMeaning,
is_spam_folder: bool,
) -> Result<bool> {
if !context.get_config_bool(Config::OnlyFetchMvbox).await? {
return Ok(false);
@@ -2353,7 +2281,7 @@ async fn should_ignore_folder(
// Still respect the SentboxWatch setting.
return Ok(!context.get_config_bool(Config::SentboxWatch).await?);
}
Ok(!(context.is_mvbox(folder).await? || folder_meaning == FolderMeaning::Spam))
Ok(!(context.is_mvbox(folder).await? || is_spam_folder))
}
/// Builds a list of sequence/uid sets. The returned sets have each no more than around 1000
@@ -2636,13 +2564,14 @@ mod tests {
};
let (headers, _) = mailparse::parse_headers(bytes)?;
let actual = if let Some(config) =
target_folder_cfg(&t, folder, get_folder_meaning_by_name(folder), &headers).await?
{
t.get_config(config).await?
} else {
None
};
let is_spam_folder = folder == "Spam";
let actual =
if let Some(config) = target_folder(&t, folder, is_spam_folder, &headers).await? {
t.get_config(config).await?
} else {
None
};
let expected = if expected_destination == folder {
None
+11 -5
View File
@@ -11,9 +11,9 @@ use tokio::io::BufWriter;
use super::capabilities::Capabilities;
use super::session::Session;
use crate::context::Context;
use crate::login_param::build_tls;
use crate::net::connect_tcp;
use crate::net::session::SessionStream;
use crate::net::tls::wrap_tls;
use crate::socks::Socks5Config;
/// IMAP write and read timeout.
@@ -95,7 +95,8 @@ impl Client {
strict_tls: bool,
) -> Result<Self> {
let tcp_stream = connect_tcp(context, hostname, port, IMAP_TIMEOUT, strict_tls).await?;
let tls_stream = wrap_tls(strict_tls, hostname, tcp_stream).await?;
let tls = build_tls(strict_tls);
let tls_stream = tls.connect(hostname, tcp_stream).await?;
let buffered_stream = BufWriter::new(tls_stream);
let session_stream: Box<dyn SessionStream> = Box::new(buffered_stream);
let mut client = ImapClient::new(session_stream);
@@ -141,7 +142,9 @@ impl Client {
.context("STARTTLS command failed")?;
let tcp_stream = client.into_inner();
let tls_stream = wrap_tls(strict_tls, hostname, tcp_stream)
let tls = build_tls(strict_tls);
let tls_stream = tls
.connect(hostname, tcp_stream)
.await
.context("STARTTLS upgrade failed")?;
@@ -162,7 +165,8 @@ impl Client {
let socks5_stream = socks5_config
.connect(context, domain, port, IMAP_TIMEOUT, strict_tls)
.await?;
let tls_stream = wrap_tls(strict_tls, domain, socks5_stream).await?;
let tls = build_tls(strict_tls);
let tls_stream = tls.connect(domain, socks5_stream).await?;
let buffered_stream = BufWriter::new(tls_stream);
let session_stream: Box<dyn SessionStream> = Box::new(buffered_stream);
let mut client = ImapClient::new(session_stream);
@@ -217,7 +221,9 @@ impl Client {
.context("STARTTLS command failed")?;
let socks5_stream = client.into_inner();
let tls_stream = wrap_tls(strict_tls, hostname, socks5_stream)
let tls = build_tls(strict_tls);
let tls_stream = tls
.connect(hostname, socks5_stream)
.await
.context("STARTTLS upgrade failed")?;
let buffered_stream = BufWriter::new(tls_stream);
+2 -3
View File
@@ -7,7 +7,7 @@ use futures_lite::FutureExt;
use super::session::Session;
use super::Imap;
use crate::imap::{client::IMAP_TIMEOUT, FolderMeaning};
use crate::imap::client::IMAP_TIMEOUT;
use crate::{context::Context, scheduler::InterruptInfo};
const IDLE_TIMEOUT: Duration = Duration::from_secs(23 * 60);
@@ -113,7 +113,6 @@ impl Imap {
&mut self,
context: &Context,
watch_folder: Option<String>,
folder_meaning: FolderMeaning,
) -> InterruptInfo {
// Idle using polling. This is also needed if we're not yet configured -
// in this case, we're waiting for a configure job (and an interrupt).
@@ -174,7 +173,7 @@ impl Imap {
// will have already fetched the messages so perform_*_fetch
// will not find any new.
match self
.fetch_new_messages(context, &watch_folder, folder_meaning, false)
.fetch_new_messages(context, &watch_folder, false, false)
.await
{
Ok(res) => {
+19 -20
View File
@@ -3,7 +3,7 @@ use std::{collections::BTreeMap, time::Instant};
use anyhow::{Context as _, Result};
use futures::stream::StreamExt;
use super::{get_folder_meaning_by_attrs, get_folder_meaning_by_name};
use super::{get_folder_meaning, get_folder_meaning_by_name};
use crate::config::Config;
use crate::imap::Imap;
use crate::log::LogExt;
@@ -33,7 +33,7 @@ impl Imap {
let mut folder_configs = BTreeMap::new();
for folder in folders {
let folder_meaning = get_folder_meaning_by_attrs(folder.attributes());
let folder_meaning = get_folder_meaning(&folder);
if folder_meaning == FolderMeaning::Virtual {
// Gmail has virtual folders that should be skipped. For example,
// emails appear in the inbox and under "All Mail" as soon as it is
@@ -53,22 +53,21 @@ impl Imap {
.or_insert_with(|| folder.name().to_string());
}
let folder_meaning = match folder_meaning {
FolderMeaning::Unknown => folder_name_meaning,
_ => folder_meaning,
};
let is_drafts = folder_meaning == FolderMeaning::Drafts
|| (folder_meaning == FolderMeaning::Unknown
&& folder_name_meaning == FolderMeaning::Drafts);
let is_spam_folder = folder_meaning == FolderMeaning::Spam
|| (folder_meaning == FolderMeaning::Unknown
&& folder_name_meaning == FolderMeaning::Spam);
// Don't scan folders that are watched anyway
if !watched_folders.contains(&folder.name().to_string())
&& folder_meaning != FolderMeaning::Drafts
&& folder_meaning != FolderMeaning::Trash
{
if !watched_folders.contains(&folder.name().to_string()) && !is_drafts {
let session = self.session.as_mut().context("no session")?;
// Drain leftover unsolicited EXISTS messages
session.server_sent_unsolicited_exists(context)?;
loop {
self.fetch_move_delete(context, folder.name(), folder_meaning)
self.fetch_move_delete(context, folder.name(), is_spam_folder)
.await
.ok_or_log_msg(context, "Can't fetch new msgs in scanned folder");
@@ -81,15 +80,15 @@ impl Imap {
}
}
// Set configs for necessary folders. Or reset if the folder was deleted.
for conf in [
Config::ConfiguredSentboxFolder,
Config::ConfiguredTrashFolder,
] {
context
.set_config(conf, folder_configs.get(&conf).map(|s| s.as_str()))
.await?;
}
// Set the `ConfiguredSentboxFolder` or set it to `None` if the folder was deleted.
context
.set_config(
Config::ConfiguredSentboxFolder,
folder_configs
.get(&Config::ConfiguredSentboxFolder)
.map(|s| s.as_str()),
)
.await?;
last_scan.replace(Instant::now());
Ok(true)

Some files were not shown because too many files have changed in this diff Show More