Compare commits
2 Commits
link2xt/de
...
fix_encryp
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6e5b11a4b8 | ||
|
|
fe52c8c9c4 |
@@ -1,11 +0,0 @@
|
||||
[env]
|
||||
# In unoptimised builds tokio tends to use a lot of stack space when
|
||||
# creating some complicated futures, tokio has an open issue for this:
|
||||
# https://github.com/tokio-rs/tokio/issues/2055. Some of our tests
|
||||
# manage to not fit in the default 2MiB stack anymore due to this, so
|
||||
# while the issue is not resolved we want to work around this.
|
||||
# Because compiling optimised builds takes a very long time we prefer
|
||||
# to avoid that. Setting this environment variable ensures that when
|
||||
# invoking `cargo test` threads are allowed to have a large enough
|
||||
# stack size without needing to use an optimised build.
|
||||
RUST_MIN_STACK = "8388608"
|
||||
235
.circleci/config.yml
Normal file
@@ -0,0 +1,235 @@
|
||||
version: 2.1
|
||||
executors:
|
||||
default:
|
||||
docker:
|
||||
- image: filecoin/rust:latest
|
||||
working_directory: /mnt/crate
|
||||
doxygen:
|
||||
docker:
|
||||
- image: hrektts/doxygen
|
||||
|
||||
restore-workspace: &restore-workspace
|
||||
attach_workspace:
|
||||
at: /mnt
|
||||
|
||||
restore-cache: &restore-cache
|
||||
restore_cache:
|
||||
keys:
|
||||
- cargo-v3-{{ checksum "rust-toolchain" }}-{{ checksum "Cargo.toml" }}-{{ checksum "Cargo.lock" }}-{{ arch }}
|
||||
- repo-source-{{ .Branch }}-{{ .Revision }}
|
||||
|
||||
commands:
|
||||
test_target:
|
||||
parameters:
|
||||
target:
|
||||
type: string
|
||||
steps:
|
||||
- *restore-workspace
|
||||
- *restore-cache
|
||||
- run:
|
||||
name: Test (<< parameters.target >>)
|
||||
command: TARGET=<< parameters.target >> ci_scripts/run-rust-test.sh
|
||||
no_output_timeout: 15m
|
||||
|
||||
jobs:
|
||||
cargo_fetch:
|
||||
executor: default
|
||||
steps:
|
||||
- checkout
|
||||
- run:
|
||||
name: Update submodules
|
||||
command: git submodule update --init --recursive
|
||||
- run:
|
||||
name: Calculate dependencies
|
||||
command: cargo generate-lockfile
|
||||
- restore_cache:
|
||||
keys:
|
||||
- cargo-v3-{{ checksum "rust-toolchain" }}-{{ checksum "Cargo.toml" }}-{{ checksum "Cargo.lock" }}-{{ arch }}
|
||||
- run: rustup install $(cat rust-toolchain)
|
||||
- run: rustup default $(cat rust-toolchain)
|
||||
- run: rustup component add --toolchain $(cat rust-toolchain) rustfmt
|
||||
- run: rustup component add --toolchain $(cat rust-toolchain) clippy-preview
|
||||
- run: cargo update
|
||||
- run: cargo fetch
|
||||
- run: rustc +stable --version
|
||||
- run: rustc +$(cat rust-toolchain) --version
|
||||
# make sure this git repo doesn't grow too big
|
||||
- run: git gc
|
||||
- persist_to_workspace:
|
||||
root: /mnt
|
||||
paths:
|
||||
- crate
|
||||
- save_cache:
|
||||
key: cargo-v3-{{ checksum "rust-toolchain" }}-{{ checksum "Cargo.toml" }}-{{ checksum "Cargo.lock" }}-{{ arch }}
|
||||
paths:
|
||||
- "~/.cargo"
|
||||
- "~/.rustup"
|
||||
|
||||
rustfmt:
|
||||
executor: default
|
||||
steps:
|
||||
- *restore-workspace
|
||||
- *restore-cache
|
||||
- run:
|
||||
name: Run cargo fmt
|
||||
command: cargo fmt --all -- --check
|
||||
|
||||
test_macos:
|
||||
macos:
|
||||
xcode: "10.0.0"
|
||||
working_directory: ~/crate
|
||||
steps:
|
||||
- run:
|
||||
name: Configure environment variables
|
||||
command: |
|
||||
echo 'export PATH="${HOME}/.cargo/bin:${HOME}/.bin:${PATH}"' >> $BASH_ENV
|
||||
echo 'export CIRCLE_ARTIFACTS="/tmp"' >> $BASH_ENV
|
||||
- checkout
|
||||
- run:
|
||||
name: Install Rust
|
||||
command: |
|
||||
curl https://sh.rustup.rs -sSf | sh -s -- -y
|
||||
- run: rustup install $(cat rust-toolchain)
|
||||
- run: rustup default $(cat rust-toolchain)
|
||||
- run: cargo update
|
||||
- run: cargo fetch
|
||||
- run:
|
||||
name: Test
|
||||
command: TARGET=x86_64-apple-darwin ci_scripts/run-rust-test.sh
|
||||
|
||||
test_x86_64-unknown-linux-gnu:
|
||||
executor: default
|
||||
steps:
|
||||
- test_target:
|
||||
target: "x86_64-unknown-linux-gnu"
|
||||
|
||||
test_i686-unknown-linux-gnu:
|
||||
executor: default
|
||||
steps:
|
||||
- test_target:
|
||||
target: "i686-unknown-linux-gnu"
|
||||
|
||||
test_aarch64-linux-android:
|
||||
executor: default
|
||||
steps:
|
||||
- test_target:
|
||||
target: "aarch64-linux-android"
|
||||
|
||||
|
||||
build_doxygen:
|
||||
executor: doxygen
|
||||
steps:
|
||||
- checkout
|
||||
- run: bash ci_scripts/run-doxygen.sh
|
||||
- run: mkdir -p workspace/c-docs
|
||||
- run: cp -av deltachat-ffi/{html,xml} workspace/c-docs/
|
||||
- persist_to_workspace:
|
||||
root: workspace
|
||||
paths:
|
||||
- c-docs
|
||||
|
||||
# build_test_docs_wheel:
|
||||
# docker:
|
||||
# - image: deltachat/coredeps
|
||||
# environment:
|
||||
# TESTS: 1
|
||||
# DOCS: 1
|
||||
# working_directory: /mnt/crate
|
||||
# steps:
|
||||
# - *restore-workspace
|
||||
# - *restore-cache
|
||||
# - run:
|
||||
# name: build docs, run tests and build wheels
|
||||
# command: ci_scripts/run-python.sh
|
||||
# - run:
|
||||
# name: copying docs and wheels to workspace
|
||||
# command: |
|
||||
# mkdir -p workspace/python
|
||||
# # cp -av docs workspace/c-docs
|
||||
# cp -av python/.docker-tox/wheelhouse workspace/
|
||||
# cp -av python/doc/_build/ workspace/py-docs
|
||||
# - persist_to_workspace:
|
||||
# root: workspace
|
||||
# paths:
|
||||
# # - c-docs
|
||||
# - py-docs
|
||||
# - wheelhouse
|
||||
|
||||
remote_tests_rust:
|
||||
machine: true
|
||||
steps:
|
||||
- checkout
|
||||
- run: ci_scripts/remote_tests_rust.sh
|
||||
|
||||
remote_tests_python:
|
||||
machine: true
|
||||
steps:
|
||||
- checkout
|
||||
#- attach_workspace:
|
||||
# at: workspace
|
||||
- run: ci_scripts/remote_tests_python.sh
|
||||
# workspace/py-docs workspace/wheelhouse workspace/c-docs
|
||||
|
||||
upload_docs_wheels:
|
||||
machine: true
|
||||
steps:
|
||||
- checkout
|
||||
- attach_workspace:
|
||||
at: workspace
|
||||
- run: pyenv global 3.5.2
|
||||
- run: ls -laR workspace
|
||||
- run: ci_scripts/ci_upload.sh workspace/py-docs workspace/wheelhouse workspace/c-docs
|
||||
|
||||
clippy:
|
||||
executor: default
|
||||
steps:
|
||||
- *restore-workspace
|
||||
- *restore-cache
|
||||
- run:
|
||||
name: Run cargo clippy
|
||||
command: cargo clippy --all
|
||||
|
||||
|
||||
workflows:
|
||||
version: 2.1
|
||||
|
||||
test:
|
||||
jobs:
|
||||
- cargo_fetch
|
||||
|
||||
- remote_tests_rust
|
||||
|
||||
- remote_tests_python
|
||||
|
||||
# - upload_docs_wheels:
|
||||
# requires:
|
||||
# - build_test_docs_wheel
|
||||
# - build_doxygen
|
||||
- rustfmt:
|
||||
requires:
|
||||
- cargo_fetch
|
||||
- clippy:
|
||||
requires:
|
||||
- cargo_fetch
|
||||
|
||||
- build_doxygen
|
||||
|
||||
# Linux Desktop 64bit
|
||||
# - test_x86_64-unknown-linux-gnu:
|
||||
# requires:
|
||||
# - cargo_fetch
|
||||
|
||||
# Linux Desktop 32bit
|
||||
# - test_i686-unknown-linux-gnu:
|
||||
# requires:
|
||||
# - cargo_fetch
|
||||
|
||||
# Android 64bit
|
||||
# - test_aarch64-linux-android:
|
||||
# requires:
|
||||
# - cargo_fetch
|
||||
|
||||
# Desktop Apple
|
||||
# - test_macos:
|
||||
# requires:
|
||||
# - cargo_fetch
|
||||
13
.gitattributes
vendored
@@ -2,17 +2,9 @@
|
||||
# ensures this even if the user has not set core.autocrlf.
|
||||
* text=auto
|
||||
|
||||
# Checkout JavaScript files with LF line endings
|
||||
# to prevent `prettier` from reporting errors on Windows.
|
||||
*.js eol=lf
|
||||
*.jsx eol=lf
|
||||
*.ts eol=lf
|
||||
*.tsx eol=lf
|
||||
*.json eol=lf
|
||||
|
||||
# This directory contains email messages verbatim, and changing CRLF to
|
||||
# LF will corrupt them.
|
||||
test-data/** text=false
|
||||
test-data/* text=false
|
||||
|
||||
# binary files should be detected by git, however, to be sure, you can add them here explicitly
|
||||
*.png binary
|
||||
@@ -20,6 +12,3 @@ test-data/** text=false
|
||||
*.gif binary
|
||||
*.ico binary
|
||||
|
||||
*.py diff=python
|
||||
*.rs diff=rust
|
||||
*.md diff=markdown
|
||||
|
||||
35
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -1,35 +0,0 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Report something that isn't working.
|
||||
title: ''
|
||||
assignees: ''
|
||||
labels: bug
|
||||
---
|
||||
|
||||
<!--
|
||||
This is the chatmail core's bug report tracker.
|
||||
For Delta Chat feature requests and support, please go to the forum: https://support.delta.chat
|
||||
Please fill out as much of this form as you can (leaving out stuff that is not applicable is ok).
|
||||
-->
|
||||
|
||||
- Operating System (Linux/Mac/Windows/iOS/Android):
|
||||
- Core Version:
|
||||
- Client Version:
|
||||
|
||||
## Expected behavior
|
||||
|
||||
*What did you try to achieve?*
|
||||
|
||||
## Actual behavior
|
||||
|
||||
*What happened instead?*
|
||||
|
||||
### Steps to reproduce the problem
|
||||
|
||||
1.
|
||||
2.
|
||||
|
||||
### Screenshots
|
||||
|
||||
### Logs
|
||||
|
||||
20
.github/dependabot.yml
vendored
@@ -1,20 +0,0 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "cargo"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "monthly"
|
||||
commit-message:
|
||||
prefix: "chore(cargo)"
|
||||
open-pull-requests-limit: 50
|
||||
cooldown:
|
||||
default-days: 7
|
||||
|
||||
# Keep GitHub Actions up to date.
|
||||
# <https://docs.github.com/en/code-security/dependabot/working-with-dependabot/keeping-your-actions-up-to-date-with-dependabot>
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
cooldown:
|
||||
default-days: 7
|
||||
380
.github/workflows/ci.yml
vendored
@@ -1,380 +0,0 @@
|
||||
# GitHub Actions workflow to
|
||||
# lint Rust and Python code
|
||||
# and run Rust tests, Python tests and async Python tests.
|
||||
|
||||
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:
|
||||
branches:
|
||||
- main
|
||||
|
||||
permissions: {}
|
||||
|
||||
env:
|
||||
RUSTFLAGS: -Dwarnings
|
||||
RUST_VERSION: 1.94.0
|
||||
|
||||
# Minimum Supported Rust Version
|
||||
MSRV: 1.88.0
|
||||
|
||||
jobs:
|
||||
lint_rust:
|
||||
name: Lint Rust
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
show-progress: false
|
||||
persist-credentials: false
|
||||
- name: Install rustfmt and clippy
|
||||
run: rustup toolchain install $RUST_VERSION --profile minimal --component rustfmt --component clippy
|
||||
- run: rustup override set $RUST_VERSION
|
||||
shell: bash
|
||||
- name: Cache rust cargo artifacts
|
||||
uses: swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5
|
||||
- name: Run rustfmt
|
||||
run: cargo fmt --all -- --check
|
||||
- name: Run clippy
|
||||
run: scripts/clippy.sh
|
||||
- name: Check with all features
|
||||
run: cargo check --workspace --all-targets --all-features
|
||||
- name: Check with only default features
|
||||
run: cargo check --all-targets
|
||||
|
||||
cargo_deny:
|
||||
name: cargo deny
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
show-progress: false
|
||||
persist-credentials: false
|
||||
- uses: EmbarkStudios/cargo-deny-action@3fd3802e88374d3fe9159b834c7714ec57d6c979
|
||||
with:
|
||||
arguments: --workspace --all-features --locked
|
||||
command: check
|
||||
command-arguments: "-Dwarnings"
|
||||
|
||||
provider_database:
|
||||
name: Check provider database
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
show-progress: false
|
||||
persist-credentials: false
|
||||
- name: Install rustfmt
|
||||
run: rustup component add --toolchain stable-x86_64-unknown-linux-gnu rustfmt
|
||||
- name: Check provider database
|
||||
run: scripts/update-provider-database.sh
|
||||
|
||||
docs:
|
||||
name: Rust doc comments
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 60
|
||||
env:
|
||||
RUSTDOCFLAGS: -Dwarnings
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
show-progress: false
|
||||
persist-credentials: false
|
||||
- name: Cache rust cargo artifacts
|
||||
uses: swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5
|
||||
- name: Rustdoc
|
||||
run: cargo doc --document-private-items --no-deps
|
||||
|
||||
rust_tests:
|
||||
name: Rust tests
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- os: ubuntu-latest
|
||||
rust: latest
|
||||
- os: windows-latest
|
||||
rust: latest
|
||||
- os: macos-latest
|
||||
rust: latest
|
||||
|
||||
# Minimum Supported Rust Version
|
||||
- os: ubuntu-latest
|
||||
rust: minimum
|
||||
runs-on: ${{ matrix.os }}
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- run:
|
||||
echo "RUSTUP_TOOLCHAIN=$MSRV" >> $GITHUB_ENV
|
||||
shell: bash
|
||||
if: matrix.rust == 'minimum'
|
||||
- run:
|
||||
echo "RUSTUP_TOOLCHAIN=$RUST_VERSION" >> $GITHUB_ENV
|
||||
shell: bash
|
||||
if: matrix.rust == 'latest'
|
||||
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
show-progress: false
|
||||
persist-credentials: false
|
||||
|
||||
- name: Install Rust ${{ matrix.rust }}
|
||||
run: rustup toolchain install --profile minimal $RUSTUP_TOOLCHAIN
|
||||
shell: bash
|
||||
- run: rustup override set $RUSTUP_TOOLCHAIN
|
||||
shell: bash
|
||||
|
||||
- name: Cache rust cargo artifacts
|
||||
uses: swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5
|
||||
|
||||
- name: Install nextest
|
||||
uses: taiki-e/install-action@69e777b377e4ec209ddad9426ae3e0c1008b0ef3
|
||||
with:
|
||||
tool: nextest
|
||||
|
||||
- name: Tests
|
||||
env:
|
||||
RUST_BACKTRACE: 1
|
||||
run: cargo nextest run --workspace --locked
|
||||
|
||||
- name: Doc-Tests
|
||||
env:
|
||||
RUST_BACKTRACE: 1
|
||||
run: cargo test --workspace --locked --doc
|
||||
|
||||
- name: Test cargo vendor
|
||||
run: cargo vendor
|
||||
|
||||
c_library:
|
||||
name: Build C library
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, macos-latest]
|
||||
runs-on: ${{ matrix.os }}
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
show-progress: false
|
||||
persist-credentials: false
|
||||
|
||||
- name: Cache rust cargo artifacts
|
||||
uses: swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5
|
||||
|
||||
- name: Build C library
|
||||
run: cargo build -p deltachat_ffi
|
||||
|
||||
- name: Upload C library
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: ${{ matrix.os }}-libdeltachat.a
|
||||
path: target/debug/libdeltachat.a
|
||||
retention-days: 1
|
||||
|
||||
rpc_server:
|
||||
name: Build deltachat-rpc-server
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, macos-latest, windows-latest]
|
||||
runs-on: ${{ matrix.os }}
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
show-progress: false
|
||||
persist-credentials: false
|
||||
|
||||
- name: Cache rust cargo artifacts
|
||||
uses: swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5
|
||||
|
||||
- name: Build deltachat-rpc-server
|
||||
run: cargo build -p deltachat-rpc-server
|
||||
|
||||
- name: Upload deltachat-rpc-server
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: ${{ matrix.os }}-deltachat-rpc-server
|
||||
path: ${{ matrix.os == 'windows-latest' && 'target/debug/deltachat-rpc-server.exe' || 'target/debug/deltachat-rpc-server' }}
|
||||
retention-days: 1
|
||||
|
||||
python_lint:
|
||||
name: Python lint
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
show-progress: false
|
||||
persist-credentials: false
|
||||
|
||||
- name: Install tox
|
||||
run: pip install tox
|
||||
|
||||
- name: Lint Python bindings
|
||||
working-directory: python
|
||||
run: tox -e lint
|
||||
|
||||
- name: Lint deltachat-rpc-client
|
||||
working-directory: deltachat-rpc-client
|
||||
run: tox -e lint
|
||||
|
||||
# mypy does not work with PyPy since mypy 1.19
|
||||
# as it introduced native `librt` dependency
|
||||
# that uses CPython internals.
|
||||
# We only run mypy with CPython because of this.
|
||||
cffi_python_mypy:
|
||||
name: CFFI Python mypy
|
||||
needs: ["c_library", "python_lint"]
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
show-progress: false
|
||||
persist-credentials: false
|
||||
|
||||
- name: Download libdeltachat.a
|
||||
uses: actions/download-artifact@v7
|
||||
with:
|
||||
name: ubuntu-latest-libdeltachat.a
|
||||
path: target/debug
|
||||
|
||||
- name: Install tox
|
||||
run: pip install tox
|
||||
|
||||
- name: Run mypy
|
||||
env:
|
||||
DCC_RS_TARGET: debug
|
||||
DCC_RS_DEV: ${{ github.workspace }}
|
||||
working-directory: python
|
||||
run: tox -e mypy
|
||||
|
||||
|
||||
cffi_python_tests:
|
||||
name: CFFI Python tests
|
||||
needs: ["c_library", "python_lint"]
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
# Currently used Rust version.
|
||||
- os: ubuntu-latest
|
||||
python: 3.14
|
||||
- os: macos-latest
|
||||
python: 3.14
|
||||
|
||||
# PyPy tests
|
||||
- os: ubuntu-latest
|
||||
python: pypy3.10
|
||||
- os: macos-latest
|
||||
python: pypy3.10
|
||||
|
||||
# Minimum Supported Python Version = 3.10
|
||||
# This is the minimum version for which manylinux Python wheels are
|
||||
# built. Test it with minimum supported Rust version.
|
||||
- os: ubuntu-latest
|
||||
python: "3.10"
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
show-progress: false
|
||||
persist-credentials: false
|
||||
|
||||
- name: Download libdeltachat.a
|
||||
uses: actions/download-artifact@v7
|
||||
with:
|
||||
name: ${{ matrix.os }}-libdeltachat.a
|
||||
path: target/debug
|
||||
|
||||
- name: Install python
|
||||
uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: ${{ matrix.python }}
|
||||
|
||||
- name: Install tox
|
||||
run: pip install tox
|
||||
|
||||
- name: Run python tests
|
||||
env:
|
||||
CHATMAIL_DOMAIN: ${{ vars.CHATMAIL_DOMAIN }}
|
||||
DCC_RS_TARGET: debug
|
||||
DCC_RS_DEV: ${{ github.workspace }}
|
||||
working-directory: python
|
||||
run: tox -e doc,py
|
||||
|
||||
rpc_python_tests:
|
||||
name: JSON-RPC Python tests
|
||||
needs: ["python_lint", "rpc_server"]
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- os: ubuntu-latest
|
||||
python: 3.14
|
||||
- os: macos-latest
|
||||
python: 3.14
|
||||
- os: windows-latest
|
||||
python: 3.14
|
||||
|
||||
# PyPy tests
|
||||
- os: ubuntu-latest
|
||||
python: pypy3.10
|
||||
- os: macos-latest
|
||||
python: pypy3.10
|
||||
|
||||
# Minimum Supported Python Version = 3.10
|
||||
- os: ubuntu-latest
|
||||
python: "3.10"
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
show-progress: false
|
||||
persist-credentials: false
|
||||
|
||||
- name: Install python
|
||||
uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: ${{ matrix.python }}
|
||||
|
||||
- name: Install tox
|
||||
run: pip install tox
|
||||
|
||||
- name: Download deltachat-rpc-server
|
||||
uses: actions/download-artifact@v7
|
||||
with:
|
||||
name: ${{ matrix.os }}-deltachat-rpc-server
|
||||
path: target/debug
|
||||
|
||||
- name: Make deltachat-rpc-server executable
|
||||
if: ${{ matrix.os != 'windows-latest' }}
|
||||
run: chmod +x target/debug/deltachat-rpc-server
|
||||
|
||||
- name: Add deltachat-rpc-server to path
|
||||
if: ${{ matrix.os != 'windows-latest' }}
|
||||
run: echo ${{ github.workspace }}/target/debug >> $GITHUB_PATH
|
||||
|
||||
- name: Add deltachat-rpc-server to path
|
||||
if: ${{ matrix.os == 'windows-latest' }}
|
||||
run: |
|
||||
"${{ github.workspace }}/target/debug" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
|
||||
|
||||
- name: Run deltachat-rpc-client tests
|
||||
env:
|
||||
CHATMAIL_DOMAIN: ${{ vars.CHATMAIL_DOMAIN }}
|
||||
working-directory: deltachat-rpc-client
|
||||
run: tox -e py
|
||||
531
.github/workflows/deltachat-rpc-server.yml
vendored
@@ -1,531 +0,0 @@
|
||||
# GitHub Actions workflow
|
||||
# to build `deltachat-rpc-server` binaries
|
||||
# and upload them to the release.
|
||||
#
|
||||
# The workflow is automatically triggered on releases.
|
||||
# It can also be triggered manually
|
||||
# to produce binary artifacts for testing.
|
||||
|
||||
name: Build deltachat-rpc-server binaries
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
release:
|
||||
types: [published]
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
# Build a version statically linked against musl libc
|
||||
# to avoid problems with glibc version incompatibility.
|
||||
build_linux:
|
||||
name: Linux
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
arch: [aarch64, armv7l, armv6l, i686, x86_64]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
show-progress: false
|
||||
persist-credentials: false
|
||||
- uses: cachix/install-nix-action@2126ae7fc54c9df00dd18f7f18754393182c73cd # v31.9.1
|
||||
|
||||
- name: Build deltachat-rpc-server binaries
|
||||
run: nix build .#deltachat-rpc-server-${{ matrix.arch }}-linux
|
||||
|
||||
- name: Upload binary
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: deltachat-rpc-server-${{ matrix.arch }}-linux
|
||||
path: result/bin/deltachat-rpc-server
|
||||
if-no-files-found: error
|
||||
|
||||
build_linux_wheel:
|
||||
name: Linux wheel
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
arch: [aarch64, armv7l, armv6l, i686, x86_64]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
show-progress: false
|
||||
persist-credentials: false
|
||||
- uses: cachix/install-nix-action@2126ae7fc54c9df00dd18f7f18754393182c73cd # v31.9.1
|
||||
|
||||
- name: Build deltachat-rpc-server wheels
|
||||
run: nix build .#deltachat-rpc-server-${{ matrix.arch }}-linux-wheel
|
||||
|
||||
- name: Upload wheel
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: deltachat-rpc-server-${{ matrix.arch }}-linux-wheel
|
||||
path: result/*.whl
|
||||
if-no-files-found: error
|
||||
|
||||
build_windows:
|
||||
name: Windows
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
arch: [win32, win64]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
show-progress: false
|
||||
persist-credentials: false
|
||||
- uses: cachix/install-nix-action@2126ae7fc54c9df00dd18f7f18754393182c73cd # v31.9.1
|
||||
|
||||
- name: Build deltachat-rpc-server binaries
|
||||
run: nix build .#deltachat-rpc-server-${{ matrix.arch }}
|
||||
|
||||
- name: Upload binary
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: deltachat-rpc-server-${{ matrix.arch }}
|
||||
path: result/bin/deltachat-rpc-server.exe
|
||||
if-no-files-found: error
|
||||
|
||||
build_windows_wheel:
|
||||
name: Windows wheel
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
arch: [win32, win64]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
show-progress: false
|
||||
persist-credentials: false
|
||||
- uses: cachix/install-nix-action@2126ae7fc54c9df00dd18f7f18754393182c73cd # v31.9.1
|
||||
|
||||
- name: Build deltachat-rpc-server wheels
|
||||
run: nix build .#deltachat-rpc-server-${{ matrix.arch }}-wheel
|
||||
|
||||
- name: Upload wheel
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: deltachat-rpc-server-${{ matrix.arch }}-wheel
|
||||
path: result/*.whl
|
||||
if-no-files-found: error
|
||||
|
||||
build_macos:
|
||||
name: macOS
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
arch: [x86_64, aarch64]
|
||||
|
||||
runs-on: macos-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
show-progress: false
|
||||
persist-credentials: false
|
||||
|
||||
- name: Setup rust target
|
||||
run: rustup target add ${{ matrix.arch }}-apple-darwin
|
||||
|
||||
- name: Build
|
||||
run: cargo build --release --package deltachat-rpc-server --target ${{ matrix.arch }}-apple-darwin --features vendored
|
||||
|
||||
- name: Upload binary
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: deltachat-rpc-server-${{ matrix.arch }}-macos
|
||||
path: target/${{ matrix.arch }}-apple-darwin/release/deltachat-rpc-server
|
||||
if-no-files-found: error
|
||||
|
||||
build_android:
|
||||
name: Android
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
arch: [arm64-v8a, armeabi-v7a]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
show-progress: false
|
||||
persist-credentials: false
|
||||
- uses: cachix/install-nix-action@2126ae7fc54c9df00dd18f7f18754393182c73cd # v31.9.1
|
||||
|
||||
- name: Build deltachat-rpc-server binaries
|
||||
run: nix build .#deltachat-rpc-server-${{ matrix.arch }}-android
|
||||
|
||||
- name: Upload binary
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: deltachat-rpc-server-${{ matrix.arch }}-android
|
||||
path: result/bin/deltachat-rpc-server
|
||||
if-no-files-found: error
|
||||
|
||||
build_android_wheel:
|
||||
name: Android wheel
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
arch: [arm64-v8a, armeabi-v7a]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
show-progress: false
|
||||
persist-credentials: false
|
||||
- uses: cachix/install-nix-action@2126ae7fc54c9df00dd18f7f18754393182c73cd # v31.9.1
|
||||
|
||||
- name: Build deltachat-rpc-server wheels
|
||||
run: nix build .#deltachat-rpc-server-${{ matrix.arch }}-android-wheel
|
||||
|
||||
- name: Upload wheel
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: deltachat-rpc-server-${{ matrix.arch }}-android-wheel
|
||||
path: result/*.whl
|
||||
if-no-files-found: error
|
||||
|
||||
publish:
|
||||
name: Build wheels and upload binaries to the release
|
||||
needs: ["build_linux", "build_linux_wheel", "build_windows", "build_windows_wheel", "build_macos", "build_android", "build_android_wheel"]
|
||||
environment:
|
||||
name: pypi
|
||||
url: https://pypi.org/p/deltachat-rpc-server
|
||||
permissions:
|
||||
id-token: write
|
||||
contents: write
|
||||
runs-on: "ubuntu-latest"
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
show-progress: false
|
||||
persist-credentials: false
|
||||
- uses: cachix/install-nix-action@2126ae7fc54c9df00dd18f7f18754393182c73cd # v31.9.1
|
||||
|
||||
- name: Download Linux aarch64 binary
|
||||
uses: actions/download-artifact@v7
|
||||
with:
|
||||
name: deltachat-rpc-server-aarch64-linux
|
||||
path: deltachat-rpc-server-aarch64-linux.d
|
||||
|
||||
- name: Download Linux aarch64 wheel
|
||||
uses: actions/download-artifact@v7
|
||||
with:
|
||||
name: deltachat-rpc-server-aarch64-linux-wheel
|
||||
path: deltachat-rpc-server-aarch64-linux-wheel.d
|
||||
|
||||
- name: Download Linux armv7l binary
|
||||
uses: actions/download-artifact@v7
|
||||
with:
|
||||
name: deltachat-rpc-server-armv7l-linux
|
||||
path: deltachat-rpc-server-armv7l-linux.d
|
||||
|
||||
- name: Download Linux armv7l wheel
|
||||
uses: actions/download-artifact@v7
|
||||
with:
|
||||
name: deltachat-rpc-server-armv7l-linux-wheel
|
||||
path: deltachat-rpc-server-armv7l-linux-wheel.d
|
||||
|
||||
- name: Download Linux armv6l binary
|
||||
uses: actions/download-artifact@v7
|
||||
with:
|
||||
name: deltachat-rpc-server-armv6l-linux
|
||||
path: deltachat-rpc-server-armv6l-linux.d
|
||||
|
||||
- name: Download Linux armv6l wheel
|
||||
uses: actions/download-artifact@v7
|
||||
with:
|
||||
name: deltachat-rpc-server-armv6l-linux-wheel
|
||||
path: deltachat-rpc-server-armv6l-linux-wheel.d
|
||||
|
||||
- name: Download Linux i686 binary
|
||||
uses: actions/download-artifact@v7
|
||||
with:
|
||||
name: deltachat-rpc-server-i686-linux
|
||||
path: deltachat-rpc-server-i686-linux.d
|
||||
|
||||
- name: Download Linux i686 wheel
|
||||
uses: actions/download-artifact@v7
|
||||
with:
|
||||
name: deltachat-rpc-server-i686-linux-wheel
|
||||
path: deltachat-rpc-server-i686-linux-wheel.d
|
||||
|
||||
- name: Download Linux x86_64 binary
|
||||
uses: actions/download-artifact@v7
|
||||
with:
|
||||
name: deltachat-rpc-server-x86_64-linux
|
||||
path: deltachat-rpc-server-x86_64-linux.d
|
||||
|
||||
- name: Download Linux x86_64 wheel
|
||||
uses: actions/download-artifact@v7
|
||||
with:
|
||||
name: deltachat-rpc-server-x86_64-linux-wheel
|
||||
path: deltachat-rpc-server-x86_64-linux-wheel.d
|
||||
|
||||
- name: Download Win32 binary
|
||||
uses: actions/download-artifact@v7
|
||||
with:
|
||||
name: deltachat-rpc-server-win32
|
||||
path: deltachat-rpc-server-win32.d
|
||||
|
||||
- name: Download Win32 wheel
|
||||
uses: actions/download-artifact@v7
|
||||
with:
|
||||
name: deltachat-rpc-server-win32-wheel
|
||||
path: deltachat-rpc-server-win32-wheel.d
|
||||
|
||||
- name: Download Win64 binary
|
||||
uses: actions/download-artifact@v7
|
||||
with:
|
||||
name: deltachat-rpc-server-win64
|
||||
path: deltachat-rpc-server-win64.d
|
||||
|
||||
- name: Download Win64 wheel
|
||||
uses: actions/download-artifact@v7
|
||||
with:
|
||||
name: deltachat-rpc-server-win64-wheel
|
||||
path: deltachat-rpc-server-win64-wheel.d
|
||||
|
||||
- name: Download macOS binary for x86_64
|
||||
uses: actions/download-artifact@v7
|
||||
with:
|
||||
name: deltachat-rpc-server-x86_64-macos
|
||||
path: deltachat-rpc-server-x86_64-macos.d
|
||||
|
||||
- name: Download macOS binary for aarch64
|
||||
uses: actions/download-artifact@v7
|
||||
with:
|
||||
name: deltachat-rpc-server-aarch64-macos
|
||||
path: deltachat-rpc-server-aarch64-macos.d
|
||||
|
||||
- name: Download Android binary for arm64-v8a
|
||||
uses: actions/download-artifact@v7
|
||||
with:
|
||||
name: deltachat-rpc-server-arm64-v8a-android
|
||||
path: deltachat-rpc-server-arm64-v8a-android.d
|
||||
|
||||
- name: Download Android wheel for arm64-v8a
|
||||
uses: actions/download-artifact@v7
|
||||
with:
|
||||
name: deltachat-rpc-server-arm64-v8a-android-wheel
|
||||
path: deltachat-rpc-server-arm64-v8a-android-wheel.d
|
||||
|
||||
- name: Download Android binary for armeabi-v7a
|
||||
uses: actions/download-artifact@v7
|
||||
with:
|
||||
name: deltachat-rpc-server-armeabi-v7a-android
|
||||
path: deltachat-rpc-server-armeabi-v7a-android.d
|
||||
|
||||
- name: Download Android wheel for armeabi-v7a
|
||||
uses: actions/download-artifact@v7
|
||||
with:
|
||||
name: deltachat-rpc-server-armeabi-v7a-android-wheel
|
||||
path: deltachat-rpc-server-armeabi-v7a-android-wheel.d
|
||||
|
||||
- name: Create bin/ directory
|
||||
run: |
|
||||
mkdir -p bin
|
||||
mv deltachat-rpc-server-aarch64-linux.d/deltachat-rpc-server bin/deltachat-rpc-server-aarch64-linux
|
||||
mv deltachat-rpc-server-armv7l-linux.d/deltachat-rpc-server bin/deltachat-rpc-server-armv7l-linux
|
||||
mv deltachat-rpc-server-armv6l-linux.d/deltachat-rpc-server bin/deltachat-rpc-server-armv6l-linux
|
||||
mv deltachat-rpc-server-i686-linux.d/deltachat-rpc-server bin/deltachat-rpc-server-i686-linux
|
||||
mv deltachat-rpc-server-x86_64-linux.d/deltachat-rpc-server bin/deltachat-rpc-server-x86_64-linux
|
||||
mv deltachat-rpc-server-win32.d/deltachat-rpc-server.exe bin/deltachat-rpc-server-win32.exe
|
||||
mv deltachat-rpc-server-win64.d/deltachat-rpc-server.exe bin/deltachat-rpc-server-win64.exe
|
||||
mv deltachat-rpc-server-x86_64-macos.d/deltachat-rpc-server bin/deltachat-rpc-server-x86_64-macos
|
||||
mv deltachat-rpc-server-aarch64-macos.d/deltachat-rpc-server bin/deltachat-rpc-server-aarch64-macos
|
||||
mv deltachat-rpc-server-arm64-v8a-android.d/deltachat-rpc-server bin/deltachat-rpc-server-arm64-v8a-android
|
||||
mv deltachat-rpc-server-armeabi-v7a-android.d/deltachat-rpc-server bin/deltachat-rpc-server-armeabi-v7a-android
|
||||
|
||||
- name: List binaries
|
||||
run: ls -l bin/
|
||||
|
||||
- name: Install wheel
|
||||
run: pip install wheel
|
||||
|
||||
- name: Build deltachat-rpc-server Python wheels
|
||||
run: |
|
||||
mkdir -p dist
|
||||
mv deltachat-rpc-server-aarch64-linux-wheel.d/*.whl dist/
|
||||
mv deltachat-rpc-server-armv7l-linux-wheel.d/*.whl dist/
|
||||
mv deltachat-rpc-server-armv6l-linux-wheel.d/*.whl dist/
|
||||
mv deltachat-rpc-server-i686-linux-wheel.d/*.whl dist/
|
||||
mv deltachat-rpc-server-x86_64-linux-wheel.d/*.whl dist/
|
||||
mv deltachat-rpc-server-win64-wheel.d/*.whl dist/
|
||||
mv deltachat-rpc-server-win32-wheel.d/*.whl dist/
|
||||
mv deltachat-rpc-server-arm64-v8a-android-wheel.d/*.whl dist/
|
||||
mv deltachat-rpc-server-armeabi-v7a-android-wheel.d/*.whl dist/
|
||||
python3 scripts/wheel-rpc-server.py x86_64-darwin bin/deltachat-rpc-server-x86_64-macos
|
||||
python3 scripts/wheel-rpc-server.py aarch64-darwin bin/deltachat-rpc-server-aarch64-macos
|
||||
mv *.whl dist/
|
||||
|
||||
- name: List artifacts
|
||||
run: ls -l dist/
|
||||
|
||||
- name: Upload binaries to the GitHub release
|
||||
if: github.event_name == 'release'
|
||||
env:
|
||||
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
|
||||
REF_NAME: ${{ github.ref_name }}
|
||||
run: |
|
||||
gh release upload "$REF_NAME" \
|
||||
--repo ${{ github.repository }} \
|
||||
bin/* dist/*
|
||||
|
||||
- name: Publish deltachat-rpc-server to PyPI
|
||||
if: github.event_name == 'release'
|
||||
uses: pypa/gh-action-pypi-publish@ed0c53931b1dc9bd32cbe73a98c7f6766f8a527e
|
||||
|
||||
publish_npm_package:
|
||||
name: Build & Publish npm prebuilds and deltachat-rpc-server
|
||||
needs: ["build_linux", "build_windows", "build_macos"]
|
||||
runs-on: "ubuntu-latest"
|
||||
environment:
|
||||
name: npm-stdio-rpc-server
|
||||
url: https://www.npmjs.com/package/@deltachat/stdio-rpc-server
|
||||
permissions:
|
||||
id-token: write
|
||||
|
||||
# Needed to publish the binaries to the release.
|
||||
contents: write
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
show-progress: false
|
||||
persist-credentials: false
|
||||
- uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: "3.11"
|
||||
|
||||
- name: Download Linux aarch64 binary
|
||||
uses: actions/download-artifact@v7
|
||||
with:
|
||||
name: deltachat-rpc-server-aarch64-linux
|
||||
path: deltachat-rpc-server-aarch64-linux.d
|
||||
|
||||
- name: Download Linux armv7l binary
|
||||
uses: actions/download-artifact@v7
|
||||
with:
|
||||
name: deltachat-rpc-server-armv7l-linux
|
||||
path: deltachat-rpc-server-armv7l-linux.d
|
||||
|
||||
- name: Download Linux armv6l binary
|
||||
uses: actions/download-artifact@v7
|
||||
with:
|
||||
name: deltachat-rpc-server-armv6l-linux
|
||||
path: deltachat-rpc-server-armv6l-linux.d
|
||||
|
||||
- name: Download Linux i686 binary
|
||||
uses: actions/download-artifact@v7
|
||||
with:
|
||||
name: deltachat-rpc-server-i686-linux
|
||||
path: deltachat-rpc-server-i686-linux.d
|
||||
|
||||
- name: Download Linux x86_64 binary
|
||||
uses: actions/download-artifact@v7
|
||||
with:
|
||||
name: deltachat-rpc-server-x86_64-linux
|
||||
path: deltachat-rpc-server-x86_64-linux.d
|
||||
|
||||
- name: Download Win32 binary
|
||||
uses: actions/download-artifact@v7
|
||||
with:
|
||||
name: deltachat-rpc-server-win32
|
||||
path: deltachat-rpc-server-win32.d
|
||||
|
||||
- name: Download Win64 binary
|
||||
uses: actions/download-artifact@v7
|
||||
with:
|
||||
name: deltachat-rpc-server-win64
|
||||
path: deltachat-rpc-server-win64.d
|
||||
|
||||
- name: Download macOS binary for x86_64
|
||||
uses: actions/download-artifact@v7
|
||||
with:
|
||||
name: deltachat-rpc-server-x86_64-macos
|
||||
path: deltachat-rpc-server-x86_64-macos.d
|
||||
|
||||
- name: Download macOS binary for aarch64
|
||||
uses: actions/download-artifact@v7
|
||||
with:
|
||||
name: deltachat-rpc-server-aarch64-macos
|
||||
path: deltachat-rpc-server-aarch64-macos.d
|
||||
|
||||
- name: Download Android binary for arm64-v8a
|
||||
uses: actions/download-artifact@v7
|
||||
with:
|
||||
name: deltachat-rpc-server-arm64-v8a-android
|
||||
path: deltachat-rpc-server-arm64-v8a-android.d
|
||||
|
||||
- name: Download Android binary for armeabi-v7a
|
||||
uses: actions/download-artifact@v7
|
||||
with:
|
||||
name: deltachat-rpc-server-armeabi-v7a-android
|
||||
path: deltachat-rpc-server-armeabi-v7a-android.d
|
||||
|
||||
- name: make npm packets for prebuilds and `@deltachat/stdio-rpc-server`
|
||||
run: |
|
||||
cd deltachat-rpc-server/npm-package
|
||||
|
||||
python --version
|
||||
|
||||
python scripts/pack_binary_for_platform.py aarch64-unknown-linux-musl ../../deltachat-rpc-server-aarch64-linux.d/deltachat-rpc-server
|
||||
python scripts/pack_binary_for_platform.py armv7-unknown-linux-musleabihf ../../deltachat-rpc-server-armv7l-linux.d/deltachat-rpc-server
|
||||
python scripts/pack_binary_for_platform.py arm-unknown-linux-musleabihf ../../deltachat-rpc-server-armv6l-linux.d/deltachat-rpc-server
|
||||
python scripts/pack_binary_for_platform.py i686-unknown-linux-musl ../../deltachat-rpc-server-i686-linux.d/deltachat-rpc-server
|
||||
python scripts/pack_binary_for_platform.py x86_64-unknown-linux-musl ../../deltachat-rpc-server-x86_64-linux.d/deltachat-rpc-server
|
||||
python scripts/pack_binary_for_platform.py i686-pc-windows-gnu ../../deltachat-rpc-server-win32.d/deltachat-rpc-server.exe
|
||||
python scripts/pack_binary_for_platform.py x86_64-pc-windows-gnu ../../deltachat-rpc-server-win64.d/deltachat-rpc-server.exe
|
||||
python scripts/pack_binary_for_platform.py x86_64-apple-darwin ../../deltachat-rpc-server-x86_64-macos.d/deltachat-rpc-server
|
||||
python scripts/pack_binary_for_platform.py aarch64-apple-darwin ../../deltachat-rpc-server-aarch64-macos.d/deltachat-rpc-server
|
||||
python scripts/pack_binary_for_platform.py aarch64-linux-android ../../deltachat-rpc-server-arm64-v8a-android.d/deltachat-rpc-server
|
||||
python scripts/pack_binary_for_platform.py armv7-linux-androideabi ../../deltachat-rpc-server-armeabi-v7a-android.d/deltachat-rpc-server
|
||||
|
||||
ls -lah platform_package
|
||||
|
||||
for platform in ./platform_package/*; do npm pack "$platform"; done
|
||||
npm pack
|
||||
ls -lah
|
||||
|
||||
- name: Upload to artifacts
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: deltachat-rpc-server-npm-package
|
||||
path: deltachat-rpc-server/npm-package/*.tgz
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Upload npm packets to the GitHub release
|
||||
if: github.event_name == 'release'
|
||||
env:
|
||||
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
|
||||
REF_NAME: ${{ github.ref_name }}
|
||||
run: |
|
||||
gh release upload "$REF_NAME" \
|
||||
--repo ${{ github.repository }} \
|
||||
deltachat-rpc-server/npm-package/*.tgz
|
||||
|
||||
# Configure Node.js for publishing.
|
||||
- uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: 20
|
||||
registry-url: "https://registry.npmjs.org"
|
||||
|
||||
# Ensure npm 11.5.1 or later is installed.
|
||||
# It is needed for <https://docs.npmjs.com/trusted-publishers>
|
||||
- name: Update npm
|
||||
run: npm install -g npm@latest
|
||||
|
||||
- name: Publish npm packets for prebuilds and `@deltachat/stdio-rpc-server`
|
||||
if: github.event_name == 'release'
|
||||
working-directory: deltachat-rpc-server/npm-package
|
||||
run: |
|
||||
ls -lah platform_package
|
||||
for platform in *.tgz; do npm publish --provenance "$platform" --access public; done
|
||||
24
.github/workflows/dependabot.yml
vendored
@@ -1,24 +0,0 @@
|
||||
# GitHub Actions workflow
|
||||
# to automatically approve PRs made by Dependabot.
|
||||
|
||||
name: Dependabot auto-approve
|
||||
on: pull_request
|
||||
|
||||
permissions:
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
dependabot:
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ github.actor == 'dependabot[bot]' }}
|
||||
steps:
|
||||
- name: Dependabot metadata
|
||||
id: metadata
|
||||
uses: dependabot/fetch-metadata@v2.4.0
|
||||
with:
|
||||
github-token: "${{ secrets.GITHUB_TOKEN }}"
|
||||
- name: Approve a PR
|
||||
run: gh pr review --approve "$PR_URL"
|
||||
env:
|
||||
PR_URL: ${{github.event.pull_request.html_url}}
|
||||
GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
|
||||
47
.github/workflows/jsonrpc-client-npm-package.yml
vendored
@@ -1,47 +0,0 @@
|
||||
name: "Publish @deltachat/jsonrpc-client"
|
||||
on:
|
||||
workflow_dispatch:
|
||||
release:
|
||||
types: [published]
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
pack-module:
|
||||
name: "Publish @deltachat/jsonrpc-client"
|
||||
runs-on: ubuntu-latest
|
||||
environment:
|
||||
name: npm-jsonrpc-client
|
||||
url: https://www.npmjs.com/package/@deltachat/jsonrpc-client
|
||||
permissions:
|
||||
id-token: write
|
||||
contents: read
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
show-progress: false
|
||||
persist-credentials: false
|
||||
|
||||
- uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: 20
|
||||
registry-url: "https://registry.npmjs.org"
|
||||
|
||||
# Ensure npm 11.5.1 or later is installed.
|
||||
# It is needed for <https://docs.npmjs.com/trusted-publishers>
|
||||
- name: Update npm
|
||||
run: npm install -g npm@latest
|
||||
|
||||
- name: Install dependencies without running scripts
|
||||
working-directory: deltachat-jsonrpc/typescript
|
||||
run: npm install --ignore-scripts
|
||||
|
||||
- name: Package
|
||||
working-directory: deltachat-jsonrpc/typescript
|
||||
run: |
|
||||
npm run build
|
||||
npm pack .
|
||||
|
||||
- name: Publish
|
||||
working-directory: deltachat-jsonrpc/typescript
|
||||
run: npm publish --provenance deltachat-jsonrpc-client-* --access public
|
||||
42
.github/workflows/jsonrpc.yml
vendored
@@ -1,42 +0,0 @@
|
||||
name: JSON-RPC API Test
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
pull_request:
|
||||
branches: [main]
|
||||
|
||||
permissions: {}
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
RUST_MIN_STACK: "8388608"
|
||||
|
||||
jobs:
|
||||
build_and_test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
show-progress: false
|
||||
persist-credentials: false
|
||||
- name: Use Node.js 18.x
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: 18.x
|
||||
- name: Add Rust cache
|
||||
uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5
|
||||
- name: npm install
|
||||
working-directory: deltachat-jsonrpc/typescript
|
||||
run: npm install
|
||||
- name: Build TypeScript, run Rust tests, generate bindings
|
||||
working-directory: deltachat-jsonrpc/typescript
|
||||
run: npm run build
|
||||
- name: Run integration tests
|
||||
working-directory: deltachat-jsonrpc/typescript
|
||||
run: npm run test
|
||||
env:
|
||||
CHATMAIL_DOMAIN: ${{ vars.CHATMAIL_DOMAIN }}
|
||||
- name: Run linter
|
||||
working-directory: deltachat-jsonrpc/typescript
|
||||
run: npm run prettier:check
|
||||
109
.github/workflows/nix.yml
vendored
@@ -1,109 +0,0 @@
|
||||
name: Test Nix flake
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- flake.nix
|
||||
- flake.lock
|
||||
- .github/workflows/nix.yml
|
||||
push:
|
||||
paths:
|
||||
- flake.nix
|
||||
- flake.lock
|
||||
- .github/workflows/nix.yml
|
||||
branches:
|
||||
- main
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
format:
|
||||
name: check flake formatting
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
show-progress: false
|
||||
persist-credentials: false
|
||||
- uses: cachix/install-nix-action@2126ae7fc54c9df00dd18f7f18754393182c73cd # v31.9.1
|
||||
- run: nix fmt flake.nix -- --check
|
||||
|
||||
build:
|
||||
name: nix build
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
installable:
|
||||
# Ensure `nix develop` will work.
|
||||
- devShells.x86_64-linux.default
|
||||
|
||||
- deltachat-python
|
||||
- deltachat-repl
|
||||
- deltachat-repl-aarch64-linux
|
||||
- deltachat-repl-arm64-v8a-android
|
||||
- deltachat-repl-armeabi-v7a-android
|
||||
- deltachat-repl-armv6l-linux
|
||||
- deltachat-repl-armv7l-linux
|
||||
- deltachat-repl-i686-linux
|
||||
- deltachat-repl-win32
|
||||
- deltachat-repl-win64
|
||||
- deltachat-repl-x86_64-linux
|
||||
- deltachat-rpc-client
|
||||
- deltachat-rpc-server
|
||||
- deltachat-rpc-server-aarch64-linux
|
||||
- deltachat-rpc-server-aarch64-linux-wheel
|
||||
- deltachat-rpc-server-arm64-v8a-android
|
||||
- deltachat-rpc-server-arm64-v8a-android-wheel
|
||||
- deltachat-rpc-server-armeabi-v7a-android
|
||||
- deltachat-rpc-server-armeabi-v7a-android-wheel
|
||||
- deltachat-rpc-server-armv6l-linux
|
||||
- deltachat-rpc-server-armv6l-linux-wheel
|
||||
- deltachat-rpc-server-armv7l-linux
|
||||
- deltachat-rpc-server-armv7l-linux-wheel
|
||||
- deltachat-rpc-server-i686-linux
|
||||
- deltachat-rpc-server-i686-linux-wheel
|
||||
- deltachat-rpc-server-source
|
||||
- deltachat-rpc-server-win32
|
||||
- deltachat-rpc-server-win32-wheel
|
||||
- deltachat-rpc-server-win64
|
||||
- deltachat-rpc-server-win64-wheel
|
||||
- deltachat-rpc-server-x86_64-linux
|
||||
- deltachat-rpc-server-x86_64-linux-wheel
|
||||
- docs
|
||||
- libdeltachat
|
||||
- python-docs
|
||||
|
||||
# Fails to build
|
||||
#- deltachat-repl-x86_64-android
|
||||
#- deltachat-repl-x86-android
|
||||
#- deltachat-rpc-server-x86_64-android
|
||||
#- deltachat-rpc-server-x86-android
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
show-progress: false
|
||||
persist-credentials: false
|
||||
- uses: cachix/install-nix-action@2126ae7fc54c9df00dd18f7f18754393182c73cd # v31.9.1
|
||||
- run: nix build .#${{ matrix.installable }}
|
||||
|
||||
build-macos:
|
||||
name: nix build on macOS
|
||||
runs-on: macos-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
installable:
|
||||
- deltachat-rpc-server
|
||||
- deltachat-rpc-server-x86_64-darwin
|
||||
|
||||
# Fails to build
|
||||
# because of <https://github.com/NixOS/nixpkgs/issues/413910>.
|
||||
# - deltachat-rpc-server-aarch64-darwin
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
show-progress: false
|
||||
persist-credentials: false
|
||||
- uses: cachix/install-nix-action@2126ae7fc54c9df00dd18f7f18754393182c73cd # v31.9.1
|
||||
- run: nix build .#${{ matrix.installable }}
|
||||
@@ -1,50 +0,0 @@
|
||||
name: Publish deltachat-rpc-client to PyPI
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
release:
|
||||
types: [published]
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build distribution
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
show-progress: false
|
||||
persist-credentials: false
|
||||
- name: Install pypa/build
|
||||
run: python3 -m pip install build
|
||||
- name: Build a binary wheel and a source tarball
|
||||
working-directory: deltachat-rpc-client
|
||||
run: python3 -m build
|
||||
- name: Store the distribution packages
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: python-package-distributions
|
||||
path: deltachat-rpc-client/dist/
|
||||
|
||||
publish-to-pypi:
|
||||
name: Publish Python distribution to PyPI
|
||||
if: github.event_name == 'release'
|
||||
needs:
|
||||
- build
|
||||
runs-on: ubuntu-latest
|
||||
environment:
|
||||
name: pypi
|
||||
url: https://pypi.org/p/deltachat-rpc-client
|
||||
permissions:
|
||||
id-token: write
|
||||
|
||||
steps:
|
||||
- name: Download all the dists
|
||||
uses: actions/download-artifact@v7
|
||||
with:
|
||||
name: python-package-distributions
|
||||
path: dist/
|
||||
- name: Publish deltachat-rpc-client to PyPI
|
||||
uses: pypa/gh-action-pypi-publish@ed0c53931b1dc9bd32cbe73a98c7f6766f8a527e
|
||||
28
.github/workflows/repl.yml
vendored
@@ -1,28 +0,0 @@
|
||||
# Manually triggered GitHub Actions workflow
|
||||
# to build a Windows repl.exe which users can
|
||||
# download to debug complex bugs.
|
||||
|
||||
name: Build Windows REPL .exe
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
build_repl:
|
||||
name: Build REPL example
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
show-progress: false
|
||||
persist-credentials: false
|
||||
- uses: cachix/install-nix-action@2126ae7fc54c9df00dd18f7f18754393182c73cd # v31.9.1
|
||||
- name: Build
|
||||
run: nix build .#deltachat-repl-win64
|
||||
- name: Upload binary
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: repl.exe
|
||||
path: "result/bin/deltachat-repl.exe"
|
||||
95
.github/workflows/upload-docs.yml
vendored
@@ -1,95 +0,0 @@
|
||||
name: Build & deploy documentation on rs.delta.chat, c.delta.chat, and py.delta.chat
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- build_jsonrpc_docs_ci
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
build-rs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
show-progress: false
|
||||
persist-credentials: false
|
||||
- name: Build the documentation with cargo
|
||||
run: |
|
||||
cargo doc --package deltachat --no-deps --document-private-items
|
||||
- name: Upload to rs.delta.chat
|
||||
run: |
|
||||
mkdir -p "$HOME/.ssh"
|
||||
echo "${{ secrets.KEY }}" > "$HOME/.ssh/key"
|
||||
chmod 600 "$HOME/.ssh/key"
|
||||
rsync -avzh -e "ssh -i $HOME/.ssh/key -o StrictHostKeyChecking=no" $GITHUB_WORKSPACE/target/doc "${{ secrets.USERNAME }}@rs.delta.chat:/var/www/html/rs/"
|
||||
|
||||
build-python:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
show-progress: false
|
||||
persist-credentials: false
|
||||
fetch-depth: 0 # Fetch history to calculate VCS version number.
|
||||
- uses: cachix/install-nix-action@2126ae7fc54c9df00dd18f7f18754393182c73cd # v31.9.1
|
||||
- name: Build Python documentation
|
||||
run: nix build .#python-docs
|
||||
- name: Upload to py.delta.chat
|
||||
run: |
|
||||
mkdir -p "$HOME/.ssh"
|
||||
echo "${{ secrets.CODESPEAK_KEY }}" > "$HOME/.ssh/key"
|
||||
chmod 600 "$HOME/.ssh/key"
|
||||
rsync -avzh -e "ssh -i $HOME/.ssh/key -o StrictHostKeyChecking=no" $GITHUB_WORKSPACE/result/html/ "delta@py.delta.chat:/home/delta/build/master"
|
||||
|
||||
build-c:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
show-progress: false
|
||||
persist-credentials: false
|
||||
fetch-depth: 0 # Fetch history to calculate VCS version number.
|
||||
- uses: cachix/install-nix-action@2126ae7fc54c9df00dd18f7f18754393182c73cd # v31.9.1
|
||||
- name: Build C documentation
|
||||
run: nix build .#docs
|
||||
- name: Upload to c.delta.chat
|
||||
run: |
|
||||
mkdir -p "$HOME/.ssh"
|
||||
echo "${{ secrets.CODESPEAK_KEY }}" > "$HOME/.ssh/key"
|
||||
chmod 600 "$HOME/.ssh/key"
|
||||
rsync -avzh -e "ssh -i $HOME/.ssh/key -o StrictHostKeyChecking=no" $GITHUB_WORKSPACE/result/html/ "delta@c.delta.chat:/home/delta/build-c/master"
|
||||
|
||||
build-ts:
|
||||
runs-on: ubuntu-latest
|
||||
defaults:
|
||||
run:
|
||||
working-directory: ./deltachat-jsonrpc/typescript
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
show-progress: false
|
||||
persist-credentials: false
|
||||
fetch-depth: 0 # Fetch history to calculate VCS version number.
|
||||
- name: Use Node.js
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: '18'
|
||||
- name: npm install
|
||||
run: npm install
|
||||
- name: npm run build
|
||||
run: npm run build
|
||||
- name: Run docs script
|
||||
run: npm run docs
|
||||
- name: Upload to js.jsonrpc.delta.chat
|
||||
run: |
|
||||
mkdir -p "$HOME/.ssh"
|
||||
echo "${{ secrets.KEY }}" > "$HOME/.ssh/key"
|
||||
chmod 600 "$HOME/.ssh/key"
|
||||
rsync -avzh -e "ssh -i $HOME/.ssh/key -o StrictHostKeyChecking=no" $GITHUB_WORKSPACE/deltachat-jsonrpc/typescript/docs/ "${{ secrets.USERNAME }}@js.jsonrpc.delta.chat:/var/www/html/js-jsonrpc/"
|
||||
31
.github/workflows/upload-ffi-docs.yml
vendored
@@ -1,31 +0,0 @@
|
||||
# GitHub Actions workflow
|
||||
# to build `deltachat_ffi` crate documentation
|
||||
# and upload it to <https://cffi.delta.chat/>
|
||||
|
||||
name: Build & Deploy Documentation on cffi.delta.chat
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
show-progress: false
|
||||
persist-credentials: false
|
||||
- name: Build the documentation with cargo
|
||||
run: |
|
||||
cargo doc --package deltachat_ffi --no-deps
|
||||
- name: Upload to cffi.delta.chat
|
||||
run: |
|
||||
mkdir -p "$HOME/.ssh"
|
||||
echo "${{ secrets.KEY }}" > "$HOME/.ssh/key"
|
||||
chmod 600 "$HOME/.ssh/key"
|
||||
rsync -avzh -e "ssh -i $HOME/.ssh/key -o StrictHostKeyChecking=no" $GITHUB_WORKSPACE/target/doc/ "${{ secrets.USERNAME }}@delta.chat:/var/www/html/cffi/"
|
||||
26
.github/workflows/zizmor-scan.yml
vendored
@@ -1,26 +0,0 @@
|
||||
name: GitHub Actions Security Analysis with zizmor
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: ["main"]
|
||||
pull_request:
|
||||
branches: ["**"]
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
zizmor:
|
||||
name: Run zizmor
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
security-events: write # Required for upload-sarif (used by zizmor-action) to upload SARIF files.
|
||||
contents: read
|
||||
actions: read
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Run zizmor
|
||||
uses: zizmorcore/zizmor-action@71321a20a9ded102f6e9ce5718a2fcec2c4f70d8 # v0.5.2
|
||||
6
.github/zizmor.yml
vendored
@@ -1,6 +0,0 @@
|
||||
rules:
|
||||
unpinned-uses:
|
||||
config:
|
||||
policies:
|
||||
actions/*: ref-pin
|
||||
dependabot/*: ref-pin
|
||||
34
.gitignore
vendored
@@ -1,9 +1,5 @@
|
||||
target/
|
||||
/target
|
||||
**/*.rs.bk
|
||||
/build
|
||||
/dist
|
||||
/fuzz/fuzz_targets/corpus/
|
||||
/fuzz/fuzz_targets/crashes/
|
||||
|
||||
# ignore vi temporaries
|
||||
*~
|
||||
@@ -15,15 +11,12 @@ include
|
||||
*.db
|
||||
*.db-blobs
|
||||
|
||||
.tox
|
||||
python/.eggs
|
||||
python/.tox
|
||||
*.egg-info
|
||||
__pycache__
|
||||
python/src/deltachat/capi*.so
|
||||
python/.venv/
|
||||
python/venv/
|
||||
venv/
|
||||
env/
|
||||
|
||||
python/liveconfig*
|
||||
|
||||
@@ -32,26 +25,3 @@ deltachat-ffi/html
|
||||
deltachat-ffi/xml
|
||||
|
||||
.rsynclist
|
||||
|
||||
coverage/
|
||||
.DS_Store
|
||||
.vscode
|
||||
.zed
|
||||
python/accounts.txt
|
||||
python/all-testaccounts.txt
|
||||
tmp/
|
||||
|
||||
# from deltachat-node
|
||||
node_modules/
|
||||
node/build/
|
||||
node/dist/
|
||||
node/prebuilds/
|
||||
node/.nyc_output/
|
||||
|
||||
# Nix symlink.
|
||||
result
|
||||
|
||||
# direnv
|
||||
.envrc
|
||||
.direnv
|
||||
.aider*
|
||||
|
||||
56
.npmignore
@@ -1,56 +0,0 @@
|
||||
.circleci/
|
||||
.gitmodules
|
||||
node/.nyc_output/
|
||||
.travis.yml
|
||||
appveyor.yml
|
||||
node/build/
|
||||
node/README.md
|
||||
rustfmt.toml
|
||||
spec.md
|
||||
test-data/
|
||||
build2/
|
||||
node_modules
|
||||
.git
|
||||
.idea/
|
||||
.pytest_cache
|
||||
CMakeLists.txt
|
||||
README.md
|
||||
contrib/
|
||||
node/ci_scripts/
|
||||
coverage/
|
||||
node/.circleci
|
||||
node/appveyor.yml
|
||||
ci/
|
||||
ci_scripts/
|
||||
python/
|
||||
target
|
||||
proptest-regressions
|
||||
deltachat-ffi/Doxyfile
|
||||
scripts
|
||||
webxdc.md
|
||||
standards.md
|
||||
draft/
|
||||
node/examples/
|
||||
# deltachat-core-rust/assets # don't exclude assets, otherwise it won't build
|
||||
node/images/
|
||||
node/test/
|
||||
node/windows.md
|
||||
node/*.tar.gz
|
||||
node/old_docs.md
|
||||
.vscode/
|
||||
.github/
|
||||
node/.prettierrc.yml
|
||||
|
||||
deltachat-jsonrpc/TODO.md
|
||||
deltachat-jsonrpc/README.MD
|
||||
deltachat-jsonrpc/.gitignore
|
||||
deltachat-jsonrpc/typescript/.gitignore
|
||||
deltachat-jsonrpc/typescript/.prettierignore
|
||||
deltachat-jsonrpc/typescript/accounts/
|
||||
deltachat-jsonrpc/typescript/index.html
|
||||
deltachat-jsonrpc/typescript/node-demo.js
|
||||
deltachat-jsonrpc/typescript/report_api_coverage.mjs
|
||||
deltachat-jsonrpc/typescript/test
|
||||
deltachat-jsonrpc/typescript/example.ts
|
||||
|
||||
.DS_Store
|
||||
7851
CHANGELOG.md
@@ -1,46 +0,0 @@
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
project(deltachat LANGUAGES C)
|
||||
include(GNUInstallDirs)
|
||||
|
||||
find_program(CARGO cargo)
|
||||
|
||||
if(APPLE)
|
||||
set(DYNAMIC_EXT "dylib")
|
||||
elseif(UNIX)
|
||||
set(DYNAMIC_EXT "so")
|
||||
else()
|
||||
set(DYNAMIC_EXT "dll")
|
||||
endif()
|
||||
|
||||
if(DEFINED ENV{CARGO_BUILD_TARGET})
|
||||
set(ARCH_DIR "$ENV{CARGO_BUILD_TARGET}")
|
||||
else()
|
||||
set(ARCH_DIR "./")
|
||||
endif()
|
||||
|
||||
add_custom_command(
|
||||
OUTPUT
|
||||
"${CMAKE_BINARY_DIR}/target/release/libdeltachat.a"
|
||||
"${CMAKE_BINARY_DIR}/target/release/libdeltachat.${DYNAMIC_EXT}"
|
||||
"${CMAKE_BINARY_DIR}/target/release/pkgconfig/deltachat.pc"
|
||||
COMMAND
|
||||
PREFIX=${CMAKE_INSTALL_PREFIX}
|
||||
LIBDIR=${CMAKE_INSTALL_FULL_LIBDIR}
|
||||
INCLUDEDIR=${CMAKE_INSTALL_FULL_INCLUDEDIR}
|
||||
${CARGO} build --target-dir=${CMAKE_BINARY_DIR}/target --release
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/deltachat-ffi
|
||||
)
|
||||
|
||||
add_custom_target(
|
||||
lib_deltachat
|
||||
ALL
|
||||
DEPENDS
|
||||
"${CMAKE_BINARY_DIR}/target/release/libdeltachat.a"
|
||||
"${CMAKE_BINARY_DIR}/target/release/libdeltachat.${DYNAMIC_EXT}"
|
||||
"${CMAKE_BINARY_DIR}/target/release/pkgconfig/deltachat.pc"
|
||||
)
|
||||
|
||||
install(FILES "deltachat-ffi/deltachat.h" DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})
|
||||
install(FILES "${CMAKE_BINARY_DIR}/target/${ARCH_DIR}/release/libdeltachat.a" DESTINATION ${CMAKE_INSTALL_LIBDIR})
|
||||
install(FILES "${CMAKE_BINARY_DIR}/target/${ARCH_DIR}/release/libdeltachat.${DYNAMIC_EXT}" DESTINATION ${CMAKE_INSTALL_LIBDIR})
|
||||
install(FILES "${CMAKE_BINARY_DIR}/target/${ARCH_DIR}/release/pkgconfig/deltachat.pc" DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig)
|
||||
122
CONTRIBUTING.md
@@ -1,122 +0,0 @@
|
||||
# Contributing to chatmail core
|
||||
|
||||
## Bug reports
|
||||
|
||||
If you found a bug, [report it on GitHub](https://github.com/chatmail/core/issues).
|
||||
If the bug you found is specific to
|
||||
[Android](https://github.com/deltachat/deltachat-android/issues),
|
||||
[iOS](https://github.com/deltachat/deltachat-ios/issues) or
|
||||
[Desktop](https://github.com/deltachat/deltachat-desktop/issues),
|
||||
report it to the corresponding repository.
|
||||
|
||||
## Feature proposals
|
||||
|
||||
If you have a feature request, create a new topic on the [forum](https://support.delta.chat/).
|
||||
|
||||
## Code contributions
|
||||
|
||||
If you want to contribute a code, follow this guide.
|
||||
|
||||
1. **Select an issue to work on.**
|
||||
|
||||
If you have an write access to the repository, assign the issue to yourself.
|
||||
Otherwise state in the comment that you are going to work on the issue
|
||||
to avoid duplicate work.
|
||||
|
||||
If the issue does not exist yet, create it first.
|
||||
|
||||
2. **Write the code.**
|
||||
|
||||
Follow the [coding conventions](STYLE.md) when writing the code.
|
||||
|
||||
3. **Commit the code.**
|
||||
|
||||
If you have write access to the repository,
|
||||
push a branch named `<username>/<feature>`
|
||||
so it is clear who is responsible for the branch,
|
||||
and open a PR proposing to merge the change.
|
||||
Otherwise fork the repository and create a branch in your fork.
|
||||
|
||||
Commit messages follow the [Conventional Commits] notation.
|
||||
We use [git-cliff] to generate the changelog from commit messages before the release.
|
||||
|
||||
With **`git cliff --unreleased`**, you can check how the changelog entry for your commit will look.
|
||||
|
||||
The following prefix types are used:
|
||||
- `feat`: Features, e.g. "feat: Pause IO for BackupProvider". If you are unsure what's the category of your commit, you can often just use `feat`.
|
||||
- `fix`: Bug fixes, e.g. "fix: delete `smtp` rows when message sending is canceled"
|
||||
- `api`: API changes, e.g. "api(rust): add `get_msg_read_receipts(context, msg_id)`"
|
||||
- `refactor`: Refactorings, e.g. "refactor: iterate over `msg_ids` without `.iter()`"
|
||||
- `perf`: Performance improvements, e.g. "perf: improve SQLite performance with `PRAGMA synchronous=normal`"
|
||||
- `test`: Test changes and improvements to the testing framework.
|
||||
- `build`: Build system and tool configuration changes, e.g. "build(git-cliff): put "ci" commits into "CI" section of changelog"
|
||||
- `ci`: CI configuration changes, e.g. "ci: limit artifact retention time for `libdeltachat.a` to 1 day"
|
||||
- `docs`: Documentation changes, e.g. "docs: add contributing guidelines"
|
||||
- `chore`: miscellaneous tasks, e.g. "chore: add `.DS_Store` to `.gitignore`"
|
||||
|
||||
Release preparation commits are marked as "chore(release): prepare for X.Y.Z"
|
||||
as described in [releasing guide](RELEASE.md).
|
||||
|
||||
Use a `!` to mark breaking changes, e.g. "api!: Remove `dc_chat_can_send`".
|
||||
|
||||
Alternatively, breaking changes can go into the commit description, e.g.:
|
||||
|
||||
```
|
||||
fix: Fix race condition and db corruption when a message was received during backup
|
||||
|
||||
BREAKING CHANGE: You have to call `dc_stop_io()`/`dc_start_io()` before/after `dc_imex(DC_IMEX_EXPORT_BACKUP)`
|
||||
```
|
||||
|
||||
4. [**Open a Pull Request**](https://github.com/chatmail/core/pulls).
|
||||
|
||||
Refer to the corresponding issue.
|
||||
|
||||
If you intend to squash merge the PR from the web interface,
|
||||
make sure the PR title follows the conventional commits notation
|
||||
as it will end up being a commit title.
|
||||
Otherwise make sure each commit title follows the conventional commit notation.
|
||||
|
||||
5. **Make sure all CI checks succeed.**
|
||||
|
||||
CI runs the tests and checks code formatting.
|
||||
|
||||
While it is running, self-review your PR to make sure all the changes you expect are there
|
||||
and there are no accidentally committed unrelated changes and files.
|
||||
|
||||
Push the necessary fixup commits or force-push to your branch if needed.
|
||||
|
||||
6. **Ask for review.**
|
||||
|
||||
Use built-in GitHub feature to request a review from suggested reviewers.
|
||||
|
||||
If you do not have write access to the repository, ask for review in the comments.
|
||||
|
||||
7. **Merge the PR.**
|
||||
|
||||
Once a PR has an approval and passes CI, it can be merged.
|
||||
|
||||
PRs from a branch created in the main repository,
|
||||
i.e. authored by those who have write access, are merged by their authors.
|
||||
|
||||
This is to ensure that PRs are merged as intended by the author,
|
||||
e.g. as a squash merge, by rebasing from the web interface or manually from the command line.
|
||||
|
||||
If you have multiple changes in one PR, do a rebase merge.
|
||||
Otherwise, you should usually do a squash merge.
|
||||
|
||||
If PR author does not have write access to the repository,
|
||||
maintainers who reviewed the PR can merge it.
|
||||
|
||||
If you do not have access to the repository and created a PR from a fork,
|
||||
ask the maintainers to merge the PR and say how it should be merged.
|
||||
|
||||
## Other ways to contribute
|
||||
|
||||
For other ways to contribute, refer to the [website](https://delta.chat/en/contribute).
|
||||
|
||||
You can find the list of good first issues
|
||||
and a link to this guide
|
||||
on the contributing page: <https://github.com/chatmail/core/contribute>
|
||||
|
||||
[Conventional Commits]: https://www.conventionalcommits.org/
|
||||
[git-cliff]: https://git-cliff.org/
|
||||
8091
Cargo.lock
generated
260
Cargo.toml
@@ -1,217 +1,83 @@
|
||||
[package]
|
||||
name = "deltachat"
|
||||
version = "2.48.0-dev"
|
||||
edition = "2024"
|
||||
license = "MPL-2.0"
|
||||
rust-version = "1.88"
|
||||
repository = "https://github.com/chatmail/core"
|
||||
|
||||
[profile.dev]
|
||||
debug = 0
|
||||
panic = 'abort'
|
||||
opt-level = 1
|
||||
|
||||
[profile.test]
|
||||
# Make anyhow `backtrace` feature useful.
|
||||
# With `debug = 0` there are no line numbers in the backtrace
|
||||
# produced with RUST_BACKTRACE=1.
|
||||
debug = 1
|
||||
opt-level = 0
|
||||
|
||||
[profile.fuzz]
|
||||
inherits = "test"
|
||||
|
||||
# Always optimize dependencies.
|
||||
# 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"
|
||||
|
||||
[profile.release]
|
||||
lto = true
|
||||
panic = 'abort'
|
||||
opt-level = "z"
|
||||
codegen-units = 1
|
||||
strip = true
|
||||
version = "1.0.0-beta.8"
|
||||
authors = ["Delta Chat Developers (ML) <delta@codespeak.net>"]
|
||||
edition = "2018"
|
||||
license = "MPL"
|
||||
|
||||
[dependencies]
|
||||
deltachat_derive = { path = "./deltachat_derive" }
|
||||
deltachat-time = { path = "./deltachat-time" }
|
||||
deltachat-contact-tools = { workspace = true }
|
||||
format-flowed = { path = "./format-flowed" }
|
||||
ratelimit = { path = "./deltachat-ratelimit" }
|
||||
|
||||
anyhow = { workspace = true }
|
||||
async-broadcast = "0.7.2"
|
||||
async-channel = { workspace = true }
|
||||
async-imap = { version = "0.11.1", default-features = false, features = ["runtime-tokio", "compress"] }
|
||||
async-native-tls = { version = "0.6", default-features = false, features = ["runtime-tokio"] }
|
||||
async-smtp = { version = "0.10.2", default-features = false, features = ["runtime-tokio"] }
|
||||
async_zip = { version = "0.0.18", default-features = false, features = ["deflate", "tokio-fs"] }
|
||||
base64 = { workspace = true }
|
||||
blake3 = "1.8.2"
|
||||
brotli = { version = "8", default-features=false, features = ["std"] }
|
||||
bytes = "1"
|
||||
chrono = { workspace = true, features = ["alloc", "clock", "std"] }
|
||||
colorutils-rs = { version = "0.7.5", default-features = false }
|
||||
data-encoding = "2.9.0"
|
||||
escaper = "0.1"
|
||||
fast-socks5 = "1"
|
||||
fd-lock = "4"
|
||||
futures-lite = { workspace = true }
|
||||
futures = { workspace = true }
|
||||
libc = "0.2.51"
|
||||
pgp = { git = "https://github.com/rpgp/rpgp", branch = "master", default-features = false }
|
||||
hex = "0.4.0"
|
||||
http-body-util = "0.1.3"
|
||||
humansize = "2"
|
||||
hyper = "1"
|
||||
hyper-util = "0.1.16"
|
||||
image = { version = "0.25.6", default-features=false, features = ["gif", "jpeg", "ico", "png", "pnm", "webp", "bmp"] }
|
||||
iroh-gossip = { version = "0.35", default-features = false, features = ["net"] }
|
||||
iroh = { version = "0.35", default-features = false }
|
||||
kamadak-exif = "0.6.1"
|
||||
libc = { workspace = true }
|
||||
mail-builder = { version = "0.4.4", default-features = false }
|
||||
mailparse = { workspace = true }
|
||||
mime = "0.3.17"
|
||||
num_cpus = "1.17"
|
||||
num-derive = "0.4"
|
||||
num-traits = { workspace = true }
|
||||
parking_lot = "0.12.4"
|
||||
percent-encoding = "2.3"
|
||||
pgp = { version = "0.19.0", default-features = false }
|
||||
pin-project = "1"
|
||||
qrcodegen = "1.7.0"
|
||||
quick-xml = { version = "0.39", features = ["escape-html"] }
|
||||
rand-old = { package = "rand", version = "0.8" }
|
||||
rand = { workspace = true }
|
||||
regex = { workspace = true }
|
||||
rusqlite = { workspace = true, features = ["sqlcipher"] }
|
||||
sanitize-filename = { workspace = true }
|
||||
sdp = "0.17.1"
|
||||
serde_json = { workspace = true }
|
||||
serde_urlencoded = "0.7.1"
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
sha-1 = "0.10"
|
||||
sha2 = "0.10"
|
||||
shadowsocks = { version = "1.23.1", default-features = false, features = ["aead-cipher", "aead-cipher-2022"] }
|
||||
smallvec = "1.15.1"
|
||||
strum = "0.28"
|
||||
strum_macros = "0.28"
|
||||
tagger = "4.3.4"
|
||||
textwrap = "0.16.2"
|
||||
thiserror = { workspace = true }
|
||||
tokio-io-timeout = "1.2.1"
|
||||
tokio-rustls = { version = "0.26.2", default-features = false }
|
||||
tokio-stream = { version = "0.1.17", features = ["fs"] }
|
||||
astral-tokio-tar = { version = "0.6", default-features = false }
|
||||
tokio-util = { workspace = true }
|
||||
tokio = { workspace = true, features = ["fs", "rt-multi-thread", "macros"] }
|
||||
toml = "0.9"
|
||||
tracing = "0.1.41"
|
||||
url = "2"
|
||||
uuid = { version = "1", features = ["serde", "v4"] }
|
||||
walkdir = "2.5.0"
|
||||
webpki-roots = "0.26.8"
|
||||
sha2 = "0.8.0"
|
||||
rand = "0.6.5"
|
||||
smallvec = "0.6.9"
|
||||
reqwest = { version = "0.9.15", default-features = false, features = ["rustls-tls"] }
|
||||
num-derive = "0.2.5"
|
||||
num-traits = "0.2.6"
|
||||
lettre = { git = "https://github.com/deltachat/lettre", branch = "feat/mail" }
|
||||
lettre_email = { git = "https://github.com/deltachat/lettre", branch = "feat/mail" }
|
||||
async-imap = { git = "https://github.com/async-email/async-imap", branch="master" }
|
||||
async-tls = "0.6"
|
||||
async-std = { version = "1.0", features = ["unstable"] }
|
||||
base64 = "0.11"
|
||||
charset = "0.1"
|
||||
percent-encoding = "2.0"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
chrono = "0.4.6"
|
||||
failure = "0.1.5"
|
||||
failure_derive = "0.1.5"
|
||||
# TODO: make optional
|
||||
rustyline = "4.1.0"
|
||||
lazy_static = "1.4.0"
|
||||
regex = "1.1.6"
|
||||
rusqlite = { version = "0.20", features = ["bundled"] }
|
||||
r2d2_sqlite = "0.12.0"
|
||||
r2d2 = "0.8.5"
|
||||
strum = "0.16.0"
|
||||
strum_macros = "0.16.0"
|
||||
thread-local-object = "0.1.0"
|
||||
backtrace = "0.3.33"
|
||||
byteorder = "1.3.1"
|
||||
itertools = "0.8.0"
|
||||
image-meta = "0.1.0"
|
||||
quick-xml = "0.15.0"
|
||||
escaper = "0.1.0"
|
||||
bitflags = "1.1.0"
|
||||
debug_stub_derive = "0.3.0"
|
||||
sanitize-filename = "0.2.1"
|
||||
stop-token = { version = "0.1.1", features = ["unstable"] }
|
||||
rustls = "0.16.0"
|
||||
webpki-roots = "0.18.0"
|
||||
webpki = "0.21.0"
|
||||
mailparse = "0.10.1"
|
||||
|
||||
[dev-dependencies]
|
||||
anyhow = { workspace = true, features = ["backtrace"] } # Enable `backtrace` feature in tests.
|
||||
criterion = { version = "0.8.1", features = ["async_tokio"] }
|
||||
futures-lite = { workspace = true }
|
||||
log = { workspace = true }
|
||||
nu-ansi-term = { workspace = true }
|
||||
pretty_assertions = "1.4.1"
|
||||
proptest = { version = "1", default-features = false, features = ["std"] }
|
||||
tempfile = { workspace = true }
|
||||
testdir = "0.9.3"
|
||||
tokio = { workspace = true, features = ["rt-multi-thread", "macros"] }
|
||||
tempfile = "3.0"
|
||||
pretty_assertions = "0.6.1"
|
||||
pretty_env_logger = "0.3.0"
|
||||
proptest = "0.9.4"
|
||||
|
||||
[workspace]
|
||||
members = [
|
||||
"deltachat-ffi",
|
||||
"deltachat_derive",
|
||||
"deltachat-jsonrpc",
|
||||
"deltachat-rpc-server",
|
||||
"deltachat-ratelimit",
|
||||
"deltachat-repl",
|
||||
"deltachat-time",
|
||||
"format-flowed",
|
||||
"deltachat-contact-tools",
|
||||
"fuzz",
|
||||
]
|
||||
|
||||
[[bench]]
|
||||
name = "create_account"
|
||||
harness = false
|
||||
[[example]]
|
||||
name = "simple"
|
||||
path = "examples/simple.rs"
|
||||
|
||||
[[bench]]
|
||||
name = "contacts"
|
||||
harness = false
|
||||
[[example]]
|
||||
name = "repl"
|
||||
path = "examples/repl/main.rs"
|
||||
|
||||
[[bench]]
|
||||
name = "search_msgs"
|
||||
harness = false
|
||||
|
||||
[[bench]]
|
||||
name = "receive_emails"
|
||||
required-features = ["internals"]
|
||||
harness = false
|
||||
|
||||
[[bench]]
|
||||
name = "decrypting"
|
||||
required-features = ["internals"]
|
||||
harness = false
|
||||
|
||||
[[bench]]
|
||||
name = "get_chat_msgs"
|
||||
harness = false
|
||||
|
||||
[[bench]]
|
||||
name = "marknoticed_chat"
|
||||
harness = false
|
||||
|
||||
[[bench]]
|
||||
name = "get_chatlist"
|
||||
harness = false
|
||||
|
||||
[[bench]]
|
||||
name = "send_events"
|
||||
harness = false
|
||||
|
||||
[workspace.dependencies]
|
||||
anyhow = "1"
|
||||
async-channel = "2.5.0"
|
||||
base64 = "0.22"
|
||||
chrono = { version = "0.4.43", default-features = false }
|
||||
deltachat-contact-tools = { path = "deltachat-contact-tools" }
|
||||
deltachat-jsonrpc = { path = "deltachat-jsonrpc", default-features = false }
|
||||
deltachat = { path = ".", default-features = false }
|
||||
futures = "0.3.32"
|
||||
futures-lite = "2.6.1"
|
||||
libc = "0.2"
|
||||
log = "0.4"
|
||||
mailparse = "0.16.1"
|
||||
nu-ansi-term = "0.50"
|
||||
num-traits = "0.2"
|
||||
rand = "0.9"
|
||||
regex = "1.12"
|
||||
rusqlite = "0.37"
|
||||
sanitize-filename = "0.6"
|
||||
serde = "1.0"
|
||||
serde_json = "1"
|
||||
tempfile = "3.25.0"
|
||||
thiserror = "2"
|
||||
tokio = "1"
|
||||
tokio-util = "0.7.18"
|
||||
tracing-subscriber = "0.3"
|
||||
yerpc = "0.6.4"
|
||||
|
||||
[features]
|
||||
default = ["vendored"]
|
||||
internals = []
|
||||
vendored = [
|
||||
"rusqlite/bundled-sqlcipher-vendored-openssl",
|
||||
"async-native-tls/vendored"
|
||||
]
|
||||
|
||||
[lints.rust]
|
||||
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(fuzzing)'] }
|
||||
default = ["nightly", "ringbuf"]
|
||||
vendored = []
|
||||
nightly = ["pgp/nightly"]
|
||||
ringbuf = ["pgp/ringbuf"]
|
||||
|
||||
5
LICENSE
@@ -2,6 +2,9 @@ The files in this directory and under its subdirectories
|
||||
are (c) 2019 by Bjoern Petersen and contributors and released under the
|
||||
Mozilla Public License Version 2.0, see below for a copy.
|
||||
|
||||
NOTE that the files in the "libs" directory are copyrighted by third parties
|
||||
and come with their own respective licenses.
|
||||
|
||||
Mozilla Public License Version 2.0
|
||||
==================================
|
||||
|
||||
@@ -361,7 +364,7 @@ Exhibit A - Source Code Form License Notice
|
||||
|
||||
This Source Code Form is subject to the terms of the Mozilla Public
|
||||
License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
If it is not possible or desirable to put the notice in a particular
|
||||
file, then You may include the notice in a location (such as a LICENSE
|
||||
|
||||
160
README.md
@@ -1,74 +1,29 @@
|
||||
<p align="center">
|
||||
<img alt="Chatmail logo" src="https://github.com/user-attachments/assets/25742da7-a837-48cd-a503-b303af55f10d" width="300" style="float:middle;" />
|
||||
</p>
|
||||
# Delta Chat Rust
|
||||
|
||||
<p align="center">
|
||||
<a href="https://github.com/chatmail/core/actions/workflows/ci.yml">
|
||||
<img alt="Rust CI" src="https://github.com/chatmail/core/actions/workflows/ci.yml/badge.svg">
|
||||
</a>
|
||||
<a href="https://deps.rs/repo/github/chatmail/core">
|
||||
<img alt="dependency status" src="https://deps.rs/repo/github/chatmail/core/status.svg">
|
||||
</a>
|
||||
</p>
|
||||
|
||||
The chatmail core library implements low-level network and encryption protocols,
|
||||
integrated by many chat bots and higher level applications,
|
||||
allowing to securely participate in the globally scaled e-mail server network.
|
||||
We provide reproducibly-built `deltachat-rpc-server` static binaries
|
||||
that offer a stdio-based high-level JSON-RPC API for instant messaging purposes.
|
||||
|
||||
The following protocols are handled without requiring API users to know much about them:
|
||||
|
||||
- secure TLS setup with DNS caching and shadowsocks/proxy support
|
||||
|
||||
- robust [SMTP](https://github.com/chatmail/async-imap)
|
||||
and [IMAP](https://github.com/chatmail/async-smtp) handling
|
||||
|
||||
- safe and interoperable [MIME parsing](https://github.com/staktrace/mailparse)
|
||||
and [MIME building](https://github.com/stalwartlabs/mail-builder).
|
||||
|
||||
- security-audited end-to-end encryption with [rPGP](https://github.com/rpgp/rpgp)
|
||||
and [Autocrypt and SecureJoin protocols](https://securejoin.rtfd.io)
|
||||
|
||||
- ephemeral [Peer-to-Peer networking using Iroh](https://iroh.computer) for multi-device setup and
|
||||
[webxdc realtime data](https://delta.chat/en/2024-11-20-webxdc-realtime).
|
||||
|
||||
- a simulation- and real-world tested [P2P group membership
|
||||
protocol without requiring server state](https://github.com/chatmail/models/tree/main/group-membership).
|
||||
> Deltachat-core written in Rust
|
||||
|
||||
[![CircleCI build status][circle-shield]][circle] [![Appveyor build status][appveyor-shield]][appveyor]
|
||||
|
||||
## Installing Rust and Cargo
|
||||
|
||||
To download and install the official compiler for the Rust programming language, and the Cargo package manager, run the command in your user environment:
|
||||
|
||||
```
|
||||
$ curl https://sh.rustup.rs -sSf | sh
|
||||
curl https://sh.rustup.rs -sSf | sh
|
||||
```
|
||||
|
||||
> On Windows, you may need to also install **Perl** to be able to compile deltachat-core.
|
||||
|
||||
## Using the CLI client
|
||||
|
||||
Compile and run the command line utility, using `cargo`:
|
||||
Compile and run Delta Chat Core command line utility, using `cargo`:
|
||||
|
||||
```
|
||||
$ cargo run --locked -p deltachat-repl -- ~/profile-db
|
||||
```
|
||||
where ~/profile-db is the database file. The utility will create it if it does not exist.
|
||||
|
||||
Optionally, install `deltachat-repl` binary with
|
||||
```
|
||||
$ cargo install --locked --path deltachat-repl/
|
||||
```
|
||||
and run as
|
||||
```
|
||||
$ deltachat-repl ~/profile-db
|
||||
cargo run --example repl -- /path/to/db
|
||||
```
|
||||
|
||||
Configure your account (if not already configured):
|
||||
|
||||
```
|
||||
Chatmail is awaiting your commands.
|
||||
Delta Chat Core is awaiting your commands.
|
||||
> set addr your@email.org
|
||||
> set mail_pw yourpassword
|
||||
> configure
|
||||
@@ -80,43 +35,37 @@ Connect to your mail server (if already configured):
|
||||
> connect
|
||||
```
|
||||
|
||||
Export your public key to a vCard file:
|
||||
|
||||
```
|
||||
> make-vcard my.vcard 1
|
||||
```
|
||||
|
||||
Create contacts by address or vCard file:
|
||||
Create a contact:
|
||||
|
||||
```
|
||||
> addcontact yourfriends@email.org
|
||||
> import-vcard key-contact.vcard
|
||||
Command executed successfully.
|
||||
```
|
||||
|
||||
List contacts:
|
||||
|
||||
```
|
||||
> listcontacts
|
||||
Contact#Contact#11: key-contact@email.org <key-contact@email.org>
|
||||
Contact#Contact#Self: Me √ <your@email.org>
|
||||
2 key contacts.
|
||||
Contact#Contact#10: yourfriends@email.org <yourfriends@email.org>
|
||||
1 address contacts.
|
||||
Contact#10: <name unset> <yourfriends@email.org>
|
||||
Contact#1: Me √√ <your@email.org>
|
||||
```
|
||||
|
||||
Create a chat with your friend and send a message:
|
||||
|
||||
```
|
||||
> createchat 10
|
||||
Single#Chat#12 created successfully.
|
||||
> chat 12
|
||||
Selecting chat Chat#12
|
||||
Single#Chat#12: yourfriends@email.org [yourfriends@email.org] Icon: profile-db-blobs/4138c52e5bc1c576cda7dd44d088c07.png
|
||||
0 messages.
|
||||
81.252µs to create this list, 123.625µs to mark all messages as noticed.
|
||||
Single#10 created successfully.
|
||||
> chat 10
|
||||
Single#10: yourfriends@email.org [yourfriends@email.org]
|
||||
> send hi
|
||||
Message sent.
|
||||
```
|
||||
|
||||
If `yourfriend@email.org` uses DeltaChat, but does not receive message just
|
||||
sent, it is advisable to check `Spam` folder. It is known that at least
|
||||
`gmx.com` treat such test messages as spam, unless told otherwise with web
|
||||
interface.
|
||||
|
||||
List messages when inside a chat:
|
||||
|
||||
```
|
||||
@@ -129,16 +78,6 @@ For more commands type:
|
||||
> help
|
||||
```
|
||||
|
||||
## Installing libdeltachat system wide
|
||||
|
||||
```
|
||||
$ git clone https://github.com/chatmail/core.git
|
||||
$ cd deltachat-core-rust
|
||||
$ cmake -B build . -DCMAKE_INSTALL_PREFIX=/usr
|
||||
$ cmake --build build
|
||||
$ sudo cmake --install build
|
||||
```
|
||||
|
||||
## Development
|
||||
|
||||
```sh
|
||||
@@ -148,13 +87,6 @@ $ cargo test --all
|
||||
$ cargo build -p deltachat_ffi --release
|
||||
```
|
||||
|
||||
## Debugging environment variables
|
||||
|
||||
- `DCC_MIME_DEBUG`: if set outgoing and incoming message will be printed
|
||||
|
||||
- `RUST_LOG=async_imap=trace,async_smtp=trace`: enable IMAP and
|
||||
SMTP tracing in addition to info messages.
|
||||
|
||||
### Expensive tests
|
||||
|
||||
Some tests are expensive and marked with `#[ignore]`, to run these
|
||||
@@ -163,53 +95,13 @@ use the `--ignored` argument to the test binary (not to cargo itself):
|
||||
$ cargo test -- --ignored
|
||||
```
|
||||
|
||||
### Fuzzing
|
||||
|
||||
Install [`cargo-bolero`](https://github.com/camshaft/bolero) with
|
||||
```sh
|
||||
$ cargo install cargo-bolero
|
||||
```
|
||||
|
||||
Run fuzzing tests with
|
||||
```sh
|
||||
$ cd fuzz
|
||||
$ cargo bolero test fuzz_mailparse -s NONE
|
||||
```
|
||||
|
||||
Corpus is created at `fuzz/fuzz_targets/corpus`,
|
||||
you can add initial inputs there.
|
||||
For `fuzz_mailparse` target corpus can be populated with
|
||||
`../test-data/message/*.eml`.
|
||||
|
||||
## Features
|
||||
|
||||
- `vendored`: When using Openssl for TLS, this bundles a vendored version.
|
||||
- `nightly`: Enable nightly only performance and security related features.
|
||||
- `ringbuf`: Enable the use of [`slice_deque`](https://github.com/gnzlbg/slice_deque) in pgp.
|
||||
|
||||
## Update Provider Data
|
||||
|
||||
To add the updates from the
|
||||
[provider-db](https://github.com/chatmail/provider-db) to the core,
|
||||
check line `REV=` inside `./scripts/update-provider-database.sh`
|
||||
and then run the script.
|
||||
|
||||
## Language bindings and frontend projects
|
||||
|
||||
Language bindings are available for:
|
||||
|
||||
- **C** \[[📂 source](./deltachat-ffi) | [📚 docs](https://c.delta.chat)\]
|
||||
- -> libdeltachat is going to be deprecated and only exists because Android, iOS and Ubuntu Touch are still using it. If you build a new project, then please use the jsonrpc api instead.
|
||||
- **JS**: \[[📂 source](./deltachat-rpc-client) | [📦 npm](https://www.npmjs.com/package/@deltachat/jsonrpc-client) | [📚 docs](https://js.jsonrpc.delta.chat/)\]
|
||||
- **Python** \[[📂 source](./python) | [📦 pypi](https://pypi.org/project/deltachat) | [📚 docs](https://py.delta.chat)\]
|
||||
- **Go** \[[📂 source](https://github.com/deltachat/deltachat-rpc-client-go/)\]
|
||||
- **Java** and **Swift** (contained in the Android/iOS repos)
|
||||
|
||||
The following "frontend" projects make use of the Rust-library
|
||||
or its language bindings:
|
||||
|
||||
- [Android](https://github.com/deltachat/deltachat-android)
|
||||
- [iOS](https://github.com/deltachat/deltachat-ios)
|
||||
- [Desktop](https://github.com/deltachat/deltachat-desktop)
|
||||
- [Pidgin](https://code.ur.gs/lupine/purple-plugin-delta/)
|
||||
- [Telepathy](https://code.ur.gs/lupine/telepathy-padfoot/)
|
||||
- [Ubuntu Touch](https://codeberg.org/lk108/deltatouch)
|
||||
- several **Bots**
|
||||
[circle-shield]: https://img.shields.io/circleci/project/github/deltachat/deltachat-core-rust/master.svg?style=flat-square
|
||||
[circle]: https://circleci.com/gh/deltachat/deltachat-core-rust/
|
||||
[appveyor-shield]: https://ci.appveyor.com/api/projects/status/lqpegel3ld4ipxj8/branch/master?style=flat-square
|
||||
[appveyor]: https://ci.appveyor.com/project/dignifiedquire/deltachat-core-rust/branch/master
|
||||
|
||||
68
RELEASE.md
@@ -1,68 +0,0 @@
|
||||
# Releasing a new version of chatmail core
|
||||
|
||||
For example, to release version 1.116.0 of the core, do the following steps.
|
||||
|
||||
1. Resolve all [blocker issues](https://github.com/chatmail/core/labels/blocker).
|
||||
|
||||
2. Update the changelog: `git cliff --unreleased --tag 1.116.0 --prepend CHANGELOG.md` or `git cliff -u -t 1.116.0 -p CHANGELOG.md`.
|
||||
|
||||
3. add a link to compare previous with current version to the end of CHANGELOG.md:
|
||||
`[1.116.0]: https://github.com/chatmail/core/compare/v1.115.2...v1.116.0`
|
||||
|
||||
4. Update the version by running `scripts/set_core_version.py 1.116.0`.
|
||||
|
||||
5. Commit the changes as `chore(release): prepare for 1.116.0`.
|
||||
Optionally, use a separate branch like `prep-1.116.0` for this commit and open a PR for review.
|
||||
|
||||
6. Push the commit to the `main` branch.
|
||||
|
||||
7. Once the commit is on the `main` branch and passed CI, tag the release: `git tag --annotate v1.116.0`.
|
||||
|
||||
8. Push the release tag: `git push origin v1.116.0`.
|
||||
|
||||
9. Create a GitHub release: `gh release create v1.116.0 --notes ''`.
|
||||
|
||||
10. Update the version to the next development version:
|
||||
`scripts/set_core_version.py 1.117.0-dev`.
|
||||
|
||||
11. Commit and push the change:
|
||||
`git commit -m "chore: bump version to 1.117.0-dev" && git push origin main`.
|
||||
|
||||
12. Once the binaries are generated and [published](https://github.com/chatmail/core/releases),
|
||||
check Windows binaries for false positive detections at [VirusTotal].
|
||||
Either upload the binaries directly or submit a direct link to the artifact.
|
||||
You can use [old browsers interface](https://www.virustotal.com/old-browsers/)
|
||||
if there are problems with using the default website.
|
||||
If you submit a direct link and get to the page saying
|
||||
"No security vendors flagged this URL as malicious",
|
||||
it does not mean that the file itself is not detected.
|
||||
You need to go to the "details" tab
|
||||
and click on the SHA-256 hash in the "Body SHA-256" section.
|
||||
If any false positive is detected,
|
||||
open an issue to track removing it.
|
||||
See <https://github.com/chatmail/core/issues/7847>
|
||||
for an example of false positive detection issue.
|
||||
If there is a false positive "Microsoft" detection,
|
||||
mark the issue as a blocker.
|
||||
|
||||
[VirusTotal]: https://www.virustotal.com/
|
||||
|
||||
## Dealing with antivirus false positives
|
||||
|
||||
If Windows release is incorrectly detected by some antivirus, submit requests to remove detection.
|
||||
|
||||
"Microsoft" antivirus is built in Windows and will break user setups so removing its detection should be highest priority.
|
||||
To submit false positive to Microsoft, go to <https://www.microsoft.com/en-us/wdsi/filesubmission> and select "Submit file as a ... Software developer" option.
|
||||
|
||||
False positive contacts for other vendors can be found at <https://docs.virustotal.com/docs/false-positive-contacts>.
|
||||
Not all of them may be up to date, so check the links below first.
|
||||
Previously we successfully used the following contacts:
|
||||
- [ESET-NOD32](mailto:samples@eset.com)
|
||||
- [Symantec](https://symsubmit.symantec.com/)
|
||||
|
||||
## Dealing with failed releases
|
||||
|
||||
Once you make a GitHub release,
|
||||
CI will try to build and publish [PyPI](https://pypi.org/) and [npm](https://www.npmjs.com/) packages.
|
||||
If this fails for some reason, do not modify the failed tag, do not delete it and do not force-push to the `main` branch.
|
||||
Fix the build process and tag a new release instead.
|
||||
157
STYLE.md
@@ -1,157 +0,0 @@
|
||||
# Coding conventions
|
||||
|
||||
We format the code using `rustfmt`. Run `cargo fmt` prior to committing the code.
|
||||
Run `scripts/clippy.sh` to check the code for common mistakes with [Clippy].
|
||||
|
||||
[Clippy]: https://doc.rust-lang.org/clippy/
|
||||
|
||||
## SQL
|
||||
|
||||
Multi-line SQL statements should be formatted using string literals,
|
||||
for example
|
||||
```
|
||||
sql.execute(
|
||||
"CREATE TABLE messages (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
text TEXT DEFAULT '' NOT NULL -- message text
|
||||
) STRICT",
|
||||
)
|
||||
.await
|
||||
.context("CREATE TABLE messages")?;
|
||||
```
|
||||
|
||||
Do not use macros like [`concat!`](https://doc.rust-lang.org/std/macro.concat.html)
|
||||
or [`indoc!`](https://docs.rs/indoc).
|
||||
Do not escape newlines like this:
|
||||
```
|
||||
sql.execute(
|
||||
"CREATE TABLE messages ( \
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT, \
|
||||
text TEXT DEFAULT '' NOT NULL \
|
||||
) STRICT",
|
||||
)
|
||||
.await
|
||||
.context("CREATE TABLE messages")?;
|
||||
```
|
||||
Escaping newlines
|
||||
is prone to errors like this if space before backslash is missing:
|
||||
```
|
||||
"SELECT foo\
|
||||
FROM bar"
|
||||
```
|
||||
Literal above results in `SELECT fooFROM bar` string.
|
||||
This style also does not allow using `--` comments.
|
||||
|
||||
---
|
||||
|
||||
Declare new SQL tables with [`STRICT`](https://sqlite.org/stricttables.html) keyword
|
||||
to make SQLite check column types.
|
||||
|
||||
Declare primary keys with [`AUTOINCREMENT`](https://www.sqlite.org/autoinc.html) keyword.
|
||||
This avoids reuse of the row IDs and can avoid dangerous bugs
|
||||
like forwarding wrong message because the message was deleted
|
||||
and another message took its row ID.
|
||||
|
||||
Declare all new columns as `NOT NULL`
|
||||
and set the `DEFAULT` value if it is optional so the column can be skipped in `INSERT` statements.
|
||||
Dealing with `NULL` values both in SQL and in Rust is tricky and we try to avoid it.
|
||||
If column is already declared without `NOT NULL`, use `IFNULL` function to provide default value when selecting it.
|
||||
Use `HAVING COUNT(*) > 0` clause
|
||||
to [prevent aggregate functions such as `MIN` and `MAX` from returning `NULL`](https://stackoverflow.com/questions/66527856/aggregate-functions-max-etc-return-null-instead-of-no-rows).
|
||||
|
||||
Don't delete unused columns too early, but maybe after several months/releases, unused columns are
|
||||
still used by older versions, so deleting them breaks downgrading the core or importing a backup in
|
||||
an older version. Also don't change the column type, consider adding a new column with another name
|
||||
instead. Finally, never change column semantics, this is especially dangerous because the `STRICT`
|
||||
keyword doesn't help here.
|
||||
|
||||
Consider adding context to `anyhow` errors for SQL statements using `.context()` so that it's
|
||||
possible to understand from logs which statement failed. See [Errors](#errors) for more info.
|
||||
|
||||
## Errors
|
||||
|
||||
Delta Chat core mostly uses [`anyhow`](https://docs.rs/anyhow/) errors.
|
||||
When using [`Context`](https://docs.rs/anyhow/latest/anyhow/trait.Context.html),
|
||||
capitalize it but do not add a full stop as the contexts will be separated by `:`.
|
||||
For example:
|
||||
```
|
||||
.with_context(|| format!("Unable to trash message {msg_id}"))
|
||||
```
|
||||
|
||||
All errors should be handled in one of these ways:
|
||||
- With `if let Err() =` (incl. logging them into `warn!()`/`err!()`).
|
||||
- With `.log_err().ok()`.
|
||||
- Bubbled up with `?`.
|
||||
|
||||
When working with [async streams](https://docs.rs/futures/0.3.31/futures/stream/index.html),
|
||||
prefer [`try_next`](https://docs.rs/futures/0.3.31/futures/stream/trait.TryStreamExt.html#method.try_next) over `next()`, e.g. do
|
||||
```
|
||||
while let Some(event) = stream.try_next().await? {
|
||||
todo!();
|
||||
}
|
||||
```
|
||||
instead of
|
||||
```
|
||||
while let Some(event_res) = stream.next().await {
|
||||
todo!();
|
||||
}
|
||||
```
|
||||
as it allows bubbling up the error early with `?`
|
||||
with no way to accidentally skip error processing
|
||||
with early `continue` or `break`.
|
||||
Some streams reading from a connection
|
||||
return infinite number of `Some(Err(_))`
|
||||
items when connection breaks and not processing
|
||||
errors may result in infinite loop.
|
||||
|
||||
`backtrace` feature is enabled for `anyhow` crate
|
||||
and `debug = 1` option is set in the test profile.
|
||||
This allows to run `RUST_BACKTRACE=1 cargo test`
|
||||
and get a backtrace with line numbers in resultified tests
|
||||
which return `anyhow::Result`.
|
||||
|
||||
`unwrap` and `expect` are not used in the library
|
||||
because panics are difficult to debug on user devices.
|
||||
However, in the tests `.expect` may be used.
|
||||
Follow
|
||||
<https://doc.rust-lang.org/core/error/index.html#common-message-styles>
|
||||
for `.expect` message style.
|
||||
|
||||
## BTreeMap vs HashMap
|
||||
|
||||
Prefer [BTreeMap](https://doc.rust-lang.org/std/collections/struct.BTreeMap.html)
|
||||
over [HashMap](https://doc.rust-lang.org/std/collections/struct.HashMap.html)
|
||||
and [BTreeSet](https://doc.rust-lang.org/std/collections/struct.BTreeSet.html)
|
||||
over [HashSet](https://doc.rust-lang.org/std/collections/struct.HashSet.html)
|
||||
as iterating over these structures returns items in deterministic order.
|
||||
|
||||
Non-deterministic code may result in difficult to reproduce bugs,
|
||||
flaky tests, regression tests that miss bugs
|
||||
or different behavior on different devices when processing the same messages.
|
||||
|
||||
## Logging
|
||||
|
||||
For logging, use `info!`, `warn!` and `error!` macros.
|
||||
Log messages should be capitalized and have a full stop in the end. For example:
|
||||
```
|
||||
info!(context, "Ignoring addition of {added_addr:?} to {chat_id}.");
|
||||
```
|
||||
|
||||
Format anyhow errors with `{:#}` to print all the contexts like this:
|
||||
```
|
||||
error!(context, "Failed to set selfavatar timestamp: {err:#}.");
|
||||
```
|
||||
|
||||
## Documentation comments
|
||||
|
||||
All public modules, methods and fields should be documented.
|
||||
This is checked by [`missing_docs`](https://doc.rust-lang.org/rustdoc/lints.html#missing_docs) lint.
|
||||
|
||||
Private items do not have to be documented,
|
||||
but CI uses `cargo doc --document-private-items`
|
||||
to build the documentation,
|
||||
so it is preferred that new items
|
||||
are documented.
|
||||
|
||||
Follow Rust guidelines for the documentation comments:
|
||||
<https://rust-lang.github.io/rfcs/1574-more-api-documentation-conventions.html#summary-sentence>
|
||||
20
appveyor.yml
Normal file
@@ -0,0 +1,20 @@
|
||||
environment:
|
||||
matrix:
|
||||
- APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017
|
||||
|
||||
install:
|
||||
- appveyor DownloadFile https://win.rustup.rs/ -FileName rustup-init.exe
|
||||
- rustup-init -yv --default-toolchain nightly-2019-07-10
|
||||
- set PATH=%PATH%;%USERPROFILE%\.cargo\bin
|
||||
- rustc -vV
|
||||
- cargo -vV
|
||||
- cargo update
|
||||
|
||||
build: false
|
||||
|
||||
test_script:
|
||||
- cargo test --release --all
|
||||
|
||||
cache:
|
||||
- target
|
||||
- C:\Users\appveyor\.cargo\registry
|
||||
|
Before Width: | Height: | Size: 1.6 KiB |
@@ -1,60 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
width="60"
|
||||
height="60"
|
||||
viewBox="0 0 60 60"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="feather feather-archive"
|
||||
version="1.1"
|
||||
id="svg8"
|
||||
sodipodi:docname="icon-archive.svg"
|
||||
inkscape:version="1.2.2 (b0a84865, 2022-12-01)"
|
||||
inkscape:export-filename="icon-archive.png"
|
||||
inkscape:export-xdpi="409.60001"
|
||||
inkscape:export-ydpi="409.60001"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs12" />
|
||||
<sodipodi:namedview
|
||||
id="namedview10"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#000000"
|
||||
borderopacity="0.25"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
showgrid="false"
|
||||
inkscape:zoom="6.4597151"
|
||||
inkscape:cx="24.459283"
|
||||
inkscape:cy="32.509174"
|
||||
inkscape:window-width="1457"
|
||||
inkscape:window-height="860"
|
||||
inkscape:window-x="55"
|
||||
inkscape:window-y="38"
|
||||
inkscape:window-maximized="0"
|
||||
inkscape:current-layer="svg8" />
|
||||
<g
|
||||
id="g846"
|
||||
transform="translate(0.558605,0.464417)">
|
||||
<path
|
||||
style="fill:none;fill-opacity:1;stroke:#808080;stroke-width:1.78186;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 38.749006,25.398867 V 38.843194 H 20.133784 V 25.398867"
|
||||
id="path847" />
|
||||
<path
|
||||
style="fill:none;fill-opacity:1;stroke:#808080;stroke-width:1.78186;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 18.065427,20.227972 h 22.751936 v 5.170894 H 18.065427 Z"
|
||||
id="path845" />
|
||||
<path
|
||||
style="fill:#ff0000;fill-opacity:1;stroke:#808080;stroke-width:1.78186;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 27.373036,29.535581 h 4.136718"
|
||||
id="line6" />
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 2.0 KiB |
|
Before Width: | Height: | Size: 4.2 KiB |
@@ -1,149 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
enable-background="new 0 0 128 128"
|
||||
viewBox="0 0 60 60"
|
||||
version="1.1"
|
||||
id="svg878"
|
||||
sodipodi:docname="icon-broadcast.svg"
|
||||
width="60"
|
||||
height="60"
|
||||
inkscape:version="1.0.2 (e86c8708, 2021-01-15)"
|
||||
inkscape:export-filename="/Users/bpetersen/projects/deltachat-core-rust/assets/icon-broadcast.png"
|
||||
inkscape:export-xdpi="409.60001"
|
||||
inkscape:export-ydpi="409.60001">
|
||||
<metadata
|
||||
id="metadata884">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<defs
|
||||
id="defs882" />
|
||||
<sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="1329"
|
||||
inkscape:window-height="847"
|
||||
id="namedview880"
|
||||
showgrid="false"
|
||||
inkscape:zoom="5.21875"
|
||||
inkscape:cx="36.598802"
|
||||
inkscape:cy="32.191617"
|
||||
inkscape:window-x="111"
|
||||
inkscape:window-y="205"
|
||||
inkscape:window-maximized="0"
|
||||
inkscape:current-layer="svg878"
|
||||
inkscape:document-rotation="0" />
|
||||
<radialGradient
|
||||
id="c"
|
||||
cx="65.25"
|
||||
cy="89"
|
||||
r="26.440001"
|
||||
gradientTransform="matrix(0.77611266,0.11996647,-0.18999676,1.2286617,-11.305867,-60.065999)"
|
||||
gradientUnits="userSpaceOnUse">
|
||||
<stop
|
||||
stop-color="#FFC107"
|
||||
offset="0"
|
||||
id="stop833" />
|
||||
<stop
|
||||
stop-color="#FFBD06"
|
||||
offset=".3502"
|
||||
id="stop835" />
|
||||
<stop
|
||||
stop-color="#FFB104"
|
||||
offset=".6938"
|
||||
id="stop837" />
|
||||
<stop
|
||||
stop-color="#FFA000"
|
||||
offset="1"
|
||||
id="stop839" />
|
||||
</radialGradient>
|
||||
<radialGradient
|
||||
id="b"
|
||||
cx="52.5"
|
||||
cy="19.75"
|
||||
r="92.975998"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="rotate(45.323856,68.997115,75.979538)">
|
||||
<stop
|
||||
stop-color="#EF5350"
|
||||
offset="0"
|
||||
id="stop848" />
|
||||
<stop
|
||||
stop-color="#EB4F4C"
|
||||
offset=".246"
|
||||
id="stop850" />
|
||||
<stop
|
||||
stop-color="#E04341"
|
||||
offset=".4878"
|
||||
id="stop852" />
|
||||
<stop
|
||||
stop-color="#CD302F"
|
||||
offset=".7272"
|
||||
id="stop854" />
|
||||
<stop
|
||||
stop-color="#C62828"
|
||||
offset=".8004"
|
||||
id="stop856" />
|
||||
<stop
|
||||
stop-color="#C62828"
|
||||
offset="1"
|
||||
id="stop858" />
|
||||
</radialGradient>
|
||||
<radialGradient
|
||||
id="a"
|
||||
cx="16.979"
|
||||
cy="92"
|
||||
r="24.165001"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="rotate(45.323856,68.997115,75.979538)"
|
||||
xlink:href="#b">
|
||||
<stop
|
||||
stop-color="#E0E0E0"
|
||||
offset="0"
|
||||
id="stop863" />
|
||||
<stop
|
||||
stop-color="#CFCFCF"
|
||||
offset=".3112"
|
||||
id="stop865" />
|
||||
<stop
|
||||
stop-color="#A4A4A4"
|
||||
offset=".9228"
|
||||
id="stop867" />
|
||||
<stop
|
||||
stop-color="#9E9E9E"
|
||||
offset="1"
|
||||
id="stop869" />
|
||||
</radialGradient>
|
||||
<rect
|
||||
y="0"
|
||||
x="0"
|
||||
height="60"
|
||||
width="60"
|
||||
id="rect1420"
|
||||
style="fill:#7cc0bc;fill-opacity:1;stroke:none;stroke-width:1.29077" />
|
||||
<path
|
||||
id="path872"
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.336872;stroke-opacity:1"
|
||||
d="m 8.6780027,35.573064 0.032831,-11.910176 c 0.00138,-0.476406 0.4881282,-0.794259 0.9235226,-0.604877 l 4.1144877,2.345752 -0.02386,8.656315 -4.1268029,2.122946 C 9.1617452,36.370003 8.6766889,36.049472 8.6780027,35.573064 Z m 5.0469633,-1.508222 0.02386,-8.656314 31.145424,-9.537653 c 0.841472,-0.219211 1.65915,0.41667 1.656755,1.283728 l -0.06929,25.139995 c -0.0024,0.867062 -0.825942,1.500799 -1.663803,1.274581 z m 3.8042,6.892234 C 16.681121,40.104348 16.315444,38.819414 16.69043,37.591308 l 2.252234,-7.347193 c 0.2644,-0.861571 0.845185,-1.567441 1.641953,-1.989251 0.796769,-0.421808 1.706956,-0.509819 2.568531,-0.245419 l 7.263888,2.225804 c 1.775518,0.543235 2.780299,2.432591 2.232297,4.208094 L 30.3971,41.790532 c -0.545627,1.777887 -2.432591,2.780297 -4.208095,2.232298 l -7.263891,-2.225804 c -0.545033,-0.165864 -1.01825,-0.460162 -1.395948,-0.83995 z m 12.377693,-7.976728 c -0.07601,-0.07642 -0.17114,-0.133864 -0.280621,-0.167516 l -7.263891,-2.225803 c -0.233244,-0.07209 -0.421626,0.0013 -0.512275,0.04861 -0.09064,0.0474 -0.25772,0.166033 -0.327435,0.396899 l -2.252234,7.347191 c -0.108166,0.354628 0.09088,0.731541 0.447888,0.842099 l 7.263891,2.225802 c 0.354626,0.108174 0.731539,-0.09088 0.842099,-0.447888 l 2.249845,-7.344814 c 0.07453,-0.245145 0.0014,-0.504991 -0.167267,-0.67458 z" />
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 5.1 KiB |
|
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 12 KiB |
@@ -7,42 +7,42 @@
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
inkscape:export-ydpi="409.60001"
|
||||
inkscape:export-xdpi="409.60001"
|
||||
inkscape:export-filename="/Users/bpetersen/projects/deltachat-core-rust/assets/icon-device.png"
|
||||
version="1.0"
|
||||
width="60"
|
||||
height="60"
|
||||
viewBox="0 0 45 45"
|
||||
preserveAspectRatio="xMidYMid meet"
|
||||
id="svg4344"
|
||||
inkscape:version="0.92.4 5da689c313, 2019-01-14"
|
||||
sodipodi:docname="icon-device.svg"
|
||||
inkscape:version="1.0beta1 (32d4812, 2019-09-19)">
|
||||
id="svg4344"
|
||||
preserveAspectRatio="xMidYMid meet"
|
||||
viewBox="0 0 45 45"
|
||||
height="60"
|
||||
width="60"
|
||||
version="1.0"
|
||||
inkscape:export-filename="/home/kerle/Workspace/deltachat-core-rust/assets/icon-device.png"
|
||||
inkscape:export-xdpi="409.60001"
|
||||
inkscape:export-ydpi="409.60001">
|
||||
<defs
|
||||
id="defs4348" />
|
||||
<sodipodi:namedview
|
||||
inkscape:snap-global="false"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
inkscape:document-rotation="0"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="1600"
|
||||
inkscape:window-height="1035"
|
||||
id="namedview4346"
|
||||
showgrid="false"
|
||||
units="px"
|
||||
inkscape:zoom="3.959798"
|
||||
inkscape:cx="28.322498"
|
||||
inkscape:cy="24.898474"
|
||||
inkscape:window-x="45"
|
||||
inkscape:window-y="23"
|
||||
inkscape:current-layer="svg4344"
|
||||
inkscape:window-maximized="0"
|
||||
inkscape:current-layer="svg4344" />
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-x="0"
|
||||
inkscape:cy="28.471578"
|
||||
inkscape:cx="28.03926"
|
||||
inkscape:zoom="5.6"
|
||||
units="px"
|
||||
showgrid="false"
|
||||
id="namedview4346"
|
||||
inkscape:window-height="1019"
|
||||
inkscape:window-width="1248"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:pageopacity="0"
|
||||
guidetolerance="10"
|
||||
gridtolerance="10"
|
||||
objecttolerance="10"
|
||||
borderopacity="1"
|
||||
inkscape:document-rotation="0"
|
||||
bordercolor="#666666"
|
||||
pagecolor="#ffffff"
|
||||
inkscape:snap-global="false" />
|
||||
<metadata
|
||||
id="metadata4336">
|
||||
Created by potrace 1.15, written by Peter Selinger 2001-2017
|
||||
@@ -56,28 +56,35 @@ Created by potrace 1.15, written by Peter Selinger 2001-2017
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<rect
|
||||
y="-4.4408921e-16"
|
||||
x="0"
|
||||
height="45"
|
||||
width="45"
|
||||
id="rect860"
|
||||
style="opacity:1;fill:#76868b;fill-opacity:1;stroke-width:0.819271" />
|
||||
<circle
|
||||
style="opacity:1;fill:#ffffff;fill-opacity:1;stroke-width:0.731071"
|
||||
id="path859"
|
||||
cx="22.5"
|
||||
cy="22.5"
|
||||
r="22.5" />
|
||||
<circle
|
||||
r="22.5"
|
||||
cy="22.5"
|
||||
cx="22.5"
|
||||
id="path4917"
|
||||
style="fill:#364e54;fill-opacity:0.67681497;stroke-width:0.58855402"
|
||||
inkscape:export-xdpi="409"
|
||||
inkscape:export-ydpi="409" />
|
||||
<g
|
||||
fill="#000000"
|
||||
stroke="none"
|
||||
style="fill:#ffffff;fill-opacity:1"
|
||||
id="g4342"
|
||||
transform="matrix(0.00255113,0,0,-0.00255113,5.586152,38.200477)"
|
||||
id="g4342">
|
||||
style="fill:#ffffff;fill-opacity:1"
|
||||
stroke="none"
|
||||
fill="#000000">
|
||||
<path
|
||||
style="fill:#ffffff;fill-opacity:1"
|
||||
d="m 8175,12765 c -703,-114 -1248,-608 -1387,-1258 -17,-82 -21,-136 -22,-277 0,-202 15,-307 70,-470 149,-446 499,-733 1009,-828 142,-26 465,-23 619,6 691,131 1201,609 1328,1244 31,158 31,417 0,565 -114,533 -482,889 -1038,1004 -133,27 -448,35 -579,14 z"
|
||||
inkscape:connector-curvature="0"
|
||||
id="path4338"
|
||||
inkscape:connector-curvature="0" />
|
||||
d="m 8175,12765 c -703,-114 -1248,-608 -1387,-1258 -17,-82 -21,-136 -22,-277 0,-202 15,-307 70,-470 149,-446 499,-733 1009,-828 142,-26 465,-23 619,6 691,131 1201,609 1328,1244 31,158 31,417 0,565 -114,533 -482,889 -1038,1004 -133,27 -448,35 -579,14 z"
|
||||
style="fill:#ffffff;fill-opacity:1" />
|
||||
<path
|
||||
style="fill:#ffffff;fill-opacity:1"
|
||||
d="m 7070,9203 c -212,-20 -275,-27 -397,-48 -691,-117 -1400,-444 -2038,-940 -182,-142 -328,-270 -585,-517 -595,-571 -911,-974 -927,-1181 -6,-76 11,-120 69,-184 75,-80 159,-108 245,-79 109,37 263,181 632,595 539,606 774,826 1035,969 135,75 231,105 341,106 82,1 94,-2 138,-27 116,-68 161,-209 122,-376 -9,-36 -349,-868 -757,-1850 -407,-982 -785,-1892 -838,-2021 -287,-694 -513,-1389 -615,-1889 -70,-342 -90,-683 -52,-874 88,-440 381,-703 882,-792 124,-23 401,-30 562,-16 783,69 1674,461 2561,1125 796,596 1492,1354 1607,1751 43,146 -33,308 -168,360 -61,23 -100,15 -173,-36 -105,-74 -202,-170 -539,-529 -515,-551 -762,-783 -982,-927 -251,-164 -437,-186 -543,-65 -56,64 -74,131 -67,247 13,179 91,434 249,815 135,324 1588,4102 1646,4280 106,325 151,561 159,826 9,281 -22,463 -112,652 -58,122 -114,199 -211,292 -245,233 -582,343 -1044,338 -91,-1 -181,-3 -200,-5 z"
|
||||
inkscape:connector-curvature="0"
|
||||
id="path4340"
|
||||
inkscape:connector-curvature="0" />
|
||||
d="m 7070,9203 c -212,-20 -275,-27 -397,-48 -691,-117 -1400,-444 -2038,-940 -182,-142 -328,-270 -585,-517 -595,-571 -911,-974 -927,-1181 -6,-76 11,-120 69,-184 75,-80 159,-108 245,-79 109,37 263,181 632,595 539,606 774,826 1035,969 135,75 231,105 341,106 82,1 94,-2 138,-27 116,-68 161,-209 122,-376 -9,-36 -349,-868 -757,-1850 -407,-982 -785,-1892 -838,-2021 -287,-694 -513,-1389 -615,-1889 -70,-342 -90,-683 -52,-874 88,-440 381,-703 882,-792 124,-23 401,-30 562,-16 783,69 1674,461 2561,1125 796,596 1492,1354 1607,1751 43,146 -33,308 -168,360 -61,23 -100,15 -173,-36 -105,-74 -202,-170 -539,-529 -515,-551 -762,-783 -982,-927 -251,-164 -437,-186 -543,-65 -56,64 -74,131 -67,247 13,179 91,434 249,815 135,324 1588,4102 1646,4280 106,325 151,561 159,826 9,281 -22,463 -112,652 -58,122 -114,199 -211,292 -245,233 -582,343 -1044,338 -91,-1 -181,-3 -200,-5 z"
|
||||
style="fill:#ffffff;fill-opacity:1" />
|
||||
</g>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 3.4 KiB After Width: | Height: | Size: 3.6 KiB |
|
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 9.7 KiB |
@@ -7,42 +7,42 @@
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
inkscape:export-ydpi="409.60001"
|
||||
inkscape:export-xdpi="409.60001"
|
||||
inkscape:export-filename="/home/kerle/test-icon.png"
|
||||
version="1.0"
|
||||
width="60"
|
||||
height="60"
|
||||
viewBox="0 0 45 45"
|
||||
preserveAspectRatio="xMidYMid meet"
|
||||
id="svg4344"
|
||||
inkscape:version="0.92.4 5da689c313, 2019-01-14"
|
||||
sodipodi:docname="icon-saved-messages.svg"
|
||||
inkscape:version="1.0beta1 (32d4812, 2019-09-19)">
|
||||
id="svg4344"
|
||||
preserveAspectRatio="xMidYMid meet"
|
||||
viewBox="0 0 45 45"
|
||||
height="60"
|
||||
width="60"
|
||||
version="1.0"
|
||||
inkscape:export-filename="/home/kerle/test-icon.png"
|
||||
inkscape:export-xdpi="409.60001"
|
||||
inkscape:export-ydpi="409.60001">
|
||||
<defs
|
||||
id="defs4348" />
|
||||
<sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
inkscape:document-rotation="0"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="1395"
|
||||
inkscape:window-height="855"
|
||||
id="namedview4346"
|
||||
showgrid="false"
|
||||
units="px"
|
||||
inkscape:zoom="4"
|
||||
inkscape:cx="29.308676"
|
||||
inkscape:cy="49.03624"
|
||||
inkscape:window-x="89"
|
||||
inkscape:window-y="108"
|
||||
inkscape:window-maximized="0"
|
||||
inkscape:lockguides="false"
|
||||
inkscape:current-layer="svg4344"
|
||||
inkscape:lockguides="false" />
|
||||
inkscape:window-maximized="0"
|
||||
inkscape:window-y="130"
|
||||
inkscape:window-x="89"
|
||||
inkscape:cy="43.28624"
|
||||
inkscape:cx="-21.066324"
|
||||
inkscape:zoom="4"
|
||||
units="px"
|
||||
showgrid="false"
|
||||
id="namedview4346"
|
||||
inkscape:window-height="855"
|
||||
inkscape:window-width="1395"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:pageopacity="0"
|
||||
guidetolerance="10"
|
||||
gridtolerance="10"
|
||||
objecttolerance="10"
|
||||
borderopacity="1"
|
||||
inkscape:document-rotation="0"
|
||||
bordercolor="#666666"
|
||||
pagecolor="#ffffff" />
|
||||
<metadata
|
||||
id="metadata4336">
|
||||
Created by potrace 1.15, written by Peter Selinger 2001-2017
|
||||
@@ -56,16 +56,21 @@ Created by potrace 1.15, written by Peter Selinger 2001-2017
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<rect
|
||||
y="0"
|
||||
x="0"
|
||||
height="45"
|
||||
width="45"
|
||||
id="rect1420"
|
||||
style="fill:#87aade;fill-opacity:1;stroke:none;stroke-width:0.968078" />
|
||||
<circle
|
||||
style="opacity:1;fill:#ffffff;fill-opacity:1;stroke-width:0.731071"
|
||||
id="path859"
|
||||
cx="22.5"
|
||||
cy="22.5"
|
||||
r="22.5" />
|
||||
<circle
|
||||
r="22.5"
|
||||
cy="22.50293"
|
||||
cx="22.5"
|
||||
id="path4917"
|
||||
style="fill:#87aade;fill-opacity:1;stroke-width:0.58855402" />
|
||||
<path
|
||||
id="rect846"
|
||||
style="fill:#ffffff;stroke-width:0.58409804"
|
||||
inkscape:connector-curvature="0"
|
||||
d="M 13.5,7.5 V 39 h 0.08654 L 22.533801,29.370239 31.482419,39 h 0.01758 V 7.5 Z m 9.004056,4.108698 1.879508,4.876388 5.039514,0.359779 -3.879358,3.363728 1.227764,5.095749 -4.276893,-2.796643 -4.280949,2.788618 1.237229,-5.093073 -3.873949,-3.371754 5.040866,-0.350417 z"
|
||||
inkscape:connector-curvature="0" />
|
||||
style="fill:#ffffff;stroke-width:0.58409804"
|
||||
id="rect846" />
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 2.4 KiB |
|
Before Width: | Height: | Size: 2.2 KiB |
@@ -1,47 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
height="480"
|
||||
viewBox="0 -960 9600 9600"
|
||||
width="480"
|
||||
fill="#ffffff"
|
||||
version="1.1"
|
||||
id="svg1"
|
||||
sodipodi:docname="icon-email.svg"
|
||||
inkscape:version="1.4.2 (ebf0e940d0, 2025-05-08)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs1" />
|
||||
<sodipodi:namedview
|
||||
id="namedview1"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:zoom="0.99091847"
|
||||
inkscape:cx="263.392"
|
||||
inkscape:cy="177.613"
|
||||
inkscape:window-width="1884"
|
||||
inkscape:window-height="1052"
|
||||
inkscape:window-x="36"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg1" />
|
||||
<rect
|
||||
style="fill:#8c8c8c;fill-opacity:1;stroke:none;stroke-width:680.523;stroke-dasharray:none;paint-order:markers fill stroke"
|
||||
id="rect1"
|
||||
width="9951.9541"
|
||||
height="9767.4756"
|
||||
x="-71.697792"
|
||||
y="-1012.83"
|
||||
ry="0.43547946" />
|
||||
<path
|
||||
d="m 2948.0033,5553.6941 q -130.7292,0 -228.7761,-96.3953 -98.0468,-96.3953 -98.0468,-224.9223 V 2447.6234 q 0,-128.527 98.0468,-224.9223 98.0469,-96.3953 228.7761,-96.3953 h 3703.9934 q 130.7292,0 228.776,96.3953 98.0469,96.3953 98.0469,224.9223 v 2784.7531 q 0,128.527 -98.0469,224.9223 -98.0468,96.3953 -228.776,96.3953 z M 4800,3936.3952 2948.0033,2742.1646 V 5232.3765 H 6651.9967 V 2742.1646 Z m 0,-321.3176 1830.2085,-1167.4541 h -3654.97 z m -1851.9967,-872.913 v -294.5412 2784.7531 z"
|
||||
id="path1"
|
||||
style="stroke-width:5.40098" />
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.8 KiB |
|
Before Width: | Height: | Size: 6.9 KiB |
@@ -1,181 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="80mm"
|
||||
height="297mm"
|
||||
viewBox="0 0 80 297"
|
||||
version="1.1"
|
||||
id="svg71"
|
||||
inkscape:version="1.0.2 (e86c8708, 2021-01-15)"
|
||||
sodipodi:docname="icon-webxdc.svg"
|
||||
inkscape:export-filename="C:\Users\user\OneDrive - BFW-Leipzig\Documents\LogoDC\finalohnerand.png"
|
||||
inkscape:export-xdpi="96"
|
||||
inkscape:export-ydpi="96">
|
||||
<metadata
|
||||
id="metadata856">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<sodipodi:namedview
|
||||
id="namedview73"
|
||||
pagecolor="#767676"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pagecheckerboard="true"
|
||||
inkscape:document-units="mm"
|
||||
showgrid="false"
|
||||
showborder="false"
|
||||
inkscape:snap-bbox="true"
|
||||
inkscape:bbox-paths="true"
|
||||
inkscape:bbox-nodes="true"
|
||||
inkscape:snap-bbox-edge-midpoints="true"
|
||||
inkscape:snap-bbox-midpoints="true"
|
||||
inkscape:object-paths="true"
|
||||
inkscape:snap-intersection-paths="true"
|
||||
inkscape:zoom="1.4142136"
|
||||
inkscape:cx="-90.271136"
|
||||
inkscape:cy="-1233.1209"
|
||||
inkscape:window-width="1864"
|
||||
inkscape:window-height="1027"
|
||||
inkscape:window-x="56"
|
||||
inkscape:window-y="25"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="layer1"
|
||||
inkscape:snap-global="false"
|
||||
showguides="false"
|
||||
inkscape:guide-bbox="true"
|
||||
inkscape:document-rotation="0"
|
||||
units="px">
|
||||
<sodipodi:guide
|
||||
position="-154.76097,641.11689"
|
||||
orientation="0,-1"
|
||||
id="guide21118" />
|
||||
<sodipodi:guide
|
||||
position="-60.286487,633.36619"
|
||||
orientation="0,-1"
|
||||
id="guide21120" />
|
||||
</sodipodi:namedview>
|
||||
<defs
|
||||
id="defs68">
|
||||
<linearGradient
|
||||
id="linearGradient4375">
|
||||
<stop
|
||||
style="stop-color:#364e59;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop4377" />
|
||||
<stop
|
||||
style="stop-color:#364e59;stop-opacity:0;"
|
||||
offset="1"
|
||||
id="stop4379" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<g
|
||||
inkscape:label="Ebene 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1">
|
||||
<rect
|
||||
style="fill:#1a1a1a;stroke:#000000;stroke-width:0.167903"
|
||||
id="rect880"
|
||||
width="79.8321"
|
||||
height="79.8321"
|
||||
x="-64.03286"
|
||||
y="-375.9097"
|
||||
ry="0" />
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
id="path3799-2"
|
||||
d="m -24.089585,-372.59579 c -19.986026,0.24336 -36.196903,16.666 -36.196903,36.67011 0,20.00409 16.210877,36.03233 36.196903,35.78912 19.0024236,-0.076 14.5340713,-10.6146 35.538854,-0.85693 -11.50627538,-17.97454 0.390097,-20.36737 0.658079,-35.81316 0,-20.00411 -16.2108788,-36.03235 -36.196911,-35.78914 z"
|
||||
style="fill:#364e59;fill-opacity:1;stroke:none;stroke-width:1.93355"
|
||||
sodipodi:nodetypes="sscccs" />
|
||||
<path
|
||||
style="fill:none;stroke:#000000;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="M -54.193871,-325.26419 Z"
|
||||
id="path3846" />
|
||||
<path
|
||||
style="fill:none;stroke:#000000;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="M -49.397951,-326.67773 Z"
|
||||
id="path3848" />
|
||||
<path
|
||||
style="fill:none;stroke:#000000;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="m -49.397951,-326.67773 v 0 0"
|
||||
id="path3850" />
|
||||
<path
|
||||
style="fill:none;stroke:#000000;stroke-width:0.01;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m -51.35133,-325.0334 -7.964067,5.98895 z"
|
||||
id="path3965" />
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
id="path11037"
|
||||
d="m -24.089585,-372.19891 c -19.986026,0.24156 -36.196903,16.54352 -36.196903,36.40062 0,7.86524 2.543315,15.1113 6.857155,20.97971 6.577146,8.94734 11.123515,9.77363 11.123515,9.77363 1.343237,1.78324 10.270932,4.3223 10.270932,4.3223 l 16.791727,-70.86654 -0.468369,-0.33457 c 0.458597,0.26445 0.428277,-0.27515 -8.378035,-0.27515 z"
|
||||
style="fill:#7cc5cc;fill-opacity:1;stroke:none;stroke-width:1.92643"
|
||||
sodipodi:nodetypes="sssccccss" />
|
||||
<path
|
||||
style="fill:none;stroke:#000000;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="M -49.944239,-310.69957 Z"
|
||||
id="path13674" />
|
||||
<g
|
||||
id="g15178"
|
||||
transform="matrix(0.79975737,0,0,0.79975737,53.088959,-63.716396)">
|
||||
<rect
|
||||
style="fill:#364e59;fill-opacity:1;stroke-width:0.01;stroke-miterlimit:4;stroke-dasharray:none"
|
||||
id="rect15072"
|
||||
width="29.897917"
|
||||
height="6.8791666"
|
||||
x="-334.4964"
|
||||
y="-154.51025"
|
||||
transform="rotate(45)" />
|
||||
<rect
|
||||
style="fill:#364e59;fill-opacity:1;stroke-width:0.01;stroke-miterlimit:4;stroke-dasharray:none"
|
||||
id="rect15072-5"
|
||||
width="29.897917"
|
||||
height="6.8791666"
|
||||
x="147.63107"
|
||||
y="-334.4964"
|
||||
transform="rotate(-45)"
|
||||
inkscape:transform-center-x="-0.74835017"
|
||||
inkscape:transform-center-y="0.37417525" />
|
||||
</g>
|
||||
<g
|
||||
id="g22468"
|
||||
transform="translate(3.3033974)">
|
||||
<g
|
||||
id="g15178-0"
|
||||
transform="matrix(-0.79975737,0,0,0.79975737,-103.11028,-63.716404)"
|
||||
style="fill:#7cc5cc;fill-opacity:1">
|
||||
<rect
|
||||
style="fill:#7cc5cc;fill-opacity:1;stroke-width:0.01;stroke-miterlimit:4;stroke-dasharray:none"
|
||||
id="rect15072-2"
|
||||
width="29.897917"
|
||||
height="6.8791666"
|
||||
x="-334.4964"
|
||||
y="-154.51025"
|
||||
transform="rotate(45)" />
|
||||
<rect
|
||||
style="fill:#7cc5cc;fill-opacity:1;stroke-width:0.01;stroke-miterlimit:4;stroke-dasharray:none"
|
||||
id="rect15072-5-5"
|
||||
width="29.897917"
|
||||
height="6.8791666"
|
||||
x="147.63107"
|
||||
y="-334.4964"
|
||||
transform="rotate(-45)"
|
||||
inkscape:transform-center-x="-0.74835017"
|
||||
inkscape:transform-center-y="0.37417525" />
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 6.5 KiB |
@@ -1,12 +0,0 @@
|
||||
<path
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:none"
|
||||
d="m 24.015419,1.2870249 c -12.549421,0 -22.7283936,10.1789711 -22.7283936,22.7283931 0,12.549422 10.1789726,22.728395 22.7283936,22.728395 14.337742,-0.342877 9.614352,-4.702705 23.697556,0.969161 -7.545453,-13.001555 -1.082973,-13.32964 -0.969161,-23.697556 0,-12.549422 -10.178973,-22.7283931 -22.728395,-22.7283931 z" />
|
||||
<path
|
||||
style="fill:#000000;fill-opacity:1;stroke:none"
|
||||
d="M 23.982249,5.3106163 C 13.645822,5.4364005 5.2618355,13.92999 5.2618355,24.275753 c 0,10.345764 8.3839865,18.635301 18.7204135,18.509516 9.827724,-0.03951 7.516769,-5.489695 18.380082,-0.443187 -5.950849,-9.296115 0.201753,-10.533667 0.340336,-18.521947 0,-10.345766 -8.383989,-18.6353031 -18.720418,-18.5095187 z" />
|
||||
<g
|
||||
style="fill:#ffffff"
|
||||
transform="scale(1.1342891,0.88160947)">
|
||||
<path
|
||||
d="m 21.360141,23.513382 q -1.218487,-1.364705 -3.387392,-3.265543 -2.388233,-2.095797 -3.216804,-3.289913 -0.828571,-1.218486 -0.828571,-2.6563 0,-2.144536 1.998318,-3.363022 1.998317,-1.2428565 5.215121,-1.2428565 3.216804,0 5.605037,1.0966375 2.412603,1.096638 2.412603,3.021846 0,0.92605 -0.584873,1.535293 -0.584874,0.609243 -1.364705,0.609243 -1.121008,0 -2.631931,-1.681511 -1.535292,-1.705881 -2.60756,-2.388233 -1.047898,-0.706722 -2.461343,-0.706722 -1.803359,0 -2.973106,0.804201 -1.145377,0.804201 -1.145377,2.047057 0,1.169747 0.950419,2.193275 0.950419,1.023529 4.898315,3.728568 4.215963,2.899998 5.946213,4.532769 1.75462,1.632772 2.851258,3.972265 1.096638,2.339494 1.096638,4.947055 0,4.581508 -3.241174,8.090749 -3.216804,3.484871 -7.530245,3.484871 -3.923526,0 -6.628566,-2.802519 -2.705039,-2.802518 -2.705039,-7.481506 0,-4.508399 2.973106,-7.530245 2.997477,-3.021846 7.359658,-3.655459 z m 1.072268,1.121008 q -6.994112,1.145377 -6.994112,9.601672 0,4.36218 1.730251,6.774783 1.75462,2.412603 4.069744,2.412603 2.412603,0 3.972265,-2.315124 1.559663,-2.339493 1.559663,-6.311759 0,-5.751255 -4.337811,-10.162175 z" />
|
||||
</g>
|
||||
@@ -1,10 +0,0 @@
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-weight:bold;font-size:24.4118px;line-height:1.25;font-family:sans-serif;fill:#aaaaaa;fill-opacity:1;stroke:none;stroke-width:0.915439"
|
||||
x="42.325161"
|
||||
y="23.32255"
|
||||
id="text72398">get.delta.chat</text>
|
||||
<path
|
||||
id="path84310"
|
||||
style="opacity:0.25;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.915439"
|
||||
d="M 17.13769,0.00129321 C 7.6753075,0.11650893 0,7.8915283 0,17.362467 c 0,9.47094 7.6753075,17.059745 17.13769,16.944599 8.99669,-0.03598 6.880074,-5.025654 16.824785,-0.405885 -5.447648,-8.510047 0.184241,-9.642482 0.311117,-16.955289 0,-9.4709395 -7.673512,-17.0597453 -17.135895,-16.94459879 z M 17.0769,4.9986797 c 1.84214,0 3.447355,0.253959 4.815003,0.7616693 1.381603,0.5076411 2.072253,1.207862 2.072253,2.0990711 0,0.4286855 -0.167495,0.7836052 -0.50242,1.0656242 -0.334921,0.2819844 -0.724544,0.4237724 -1.171121,0.4237724 -0.641952,0 -1.396532,-0.3909376 -2.261778,-1.169353 C 19.14963,7.3898036 18.402555,6.83791 17.788507,6.5220182 17.188416,6.1950547 16.484552,6.0321266 15.675129,6.0321266 c -1.032717,0 -1.883352,0.1854523 -2.553215,0.5578447 -0.655913,0.372254 -0.98517,0.8460916 -0.98517,1.4214436 0,0.5414792 0.272815,1.0495355 0.817093,1.5233385 0.544275,0.4738026 1.946291,1.3367446 4.207097,2.5889976 2.414319,1.342419 4.117377,2.390985 5.108232,3.146807 1.004795,0.755857 1.821505,1.675853 2.449514,2.758846 0.628002,1.082993 0.942253,2.227607 0.942253,3.434674 0,2.120834 -0.929555,3.993314 -2.785656,5.617786 -1.84214,1.613228 -3.99694,2.41915 -6.467082,2.41915 -2.246845,0 -4.145607,-0.647976 -5.694677,-1.945312 -1.5490699,-1.297336 -2.3225722,-3.028063 -2.3225722,-5.194049 0,-2.087031 0.8506345,-3.83094 2.5532182,-5.229825 1.716541,-1.398884 3.824203,-2.245599 6.322256,-2.538897 -0.697774,-0.631749 -1.668763,-1.387225 -2.910816,-2.267155 -1.367648,-0.970199 -2.287914,-1.73045 -2.762402,-2.283243 -0.474491,-0.5640381 -0.711618,-1.1795944 -0.711618,-1.8451814 0,-0.9927581 0.572093,-1.7710351 1.716451,-2.3351077 1.144362,-0.5753173 2.636724,-0.8635642 4.478865,-0.8635642 z m 1.110327,10.3738083 c -4.005262,0.5302 -6.007576,2.75279 -6.007576,6.667322 0,2.01932 0.49495,3.587291 1.485805,4.704157 1.004806,1.116832 2.169696,1.675299 3.495479,1.675299 1.381602,0 2.520072,-0.535632 3.413229,-1.60738 0.893168,-1.082959 1.339187,-2.545264 1.339187,-4.384079 0,-2.662348 -1.242022,-5.013441 -3.726124,-7.055319 z" />
|
||||
@@ -1,7 +0,0 @@
|
||||
BEGIN:VCARD
|
||||
VERSION:4.0
|
||||
EMAIL:self_reporting@testrun.org
|
||||
FN:Statistics bot
|
||||
KEY:data:application/pgp-keys;base64,xjMEZbfBlBYJKwYBBAHaRw8BAQdABpLWS2PUIGGo4pslVt4R8sylP5wZihmhf1DTDr3oCMPNHDxzZWxmX3JlcG9ydGluZ0B0ZXN0cnVuLm9yZz7CiwQQFggAMwIZAQUCZbfBlAIbAwQLCQgHBhUICQoLAgMWAgEWIQTS2i16sHeYTckGn284K3M5Z4oohAAKCRA4K3M5Z4oohD8dAQCQV7CoH6UP4PD+NqI4kW5tbbqdh2AnDROg60qotmLExAEAxDfd3QHAK9f8b9qQUbLmHIztCLxhEuVbWPBEYeVW0gvOOARlt8GUEgorBgEEAZdVAQUBAQdAMBUhYoAAcI625vGZqnM5maPX4sGJ7qvJxPAFILPy6AcDAQgHwngEGBYIACAFAmW3wZQCGwwWIQTS2i16sHeYTckGn284K3M5Z4oohAAKCRA4K3M5Z4oohPwCAQCvzk1ObIkj2GqsuIfaULlgdnfdZY8LNary425CEfHZDQD5AblXVrlMO1frdlc/Vo9z3pEeCrfYdD7ITD3/OeVoiQ4=
|
||||
REV:20250412T195751Z
|
||||
END:VCARD
|
||||
|
Before Width: | Height: | Size: 121 KiB After Width: | Height: | Size: 113 KiB |
@@ -1,47 +0,0 @@
|
||||
#![recursion_limit = "256"]
|
||||
use std::hint::black_box;
|
||||
|
||||
use criterion::{Criterion, criterion_group, criterion_main};
|
||||
use deltachat::Events;
|
||||
use deltachat::contact::Contact;
|
||||
use deltachat::context::Context;
|
||||
use deltachat::stock_str::StockStrings;
|
||||
use tempfile::tempdir;
|
||||
|
||||
async fn address_book_benchmark(n: u32, read_count: u32) {
|
||||
let dir = tempdir().unwrap();
|
||||
let dbfile = dir.path().join("db.sqlite");
|
||||
let id = 100;
|
||||
let context = Context::new(&dbfile, id, Events::new(), StockStrings::new())
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let book = (0..n)
|
||||
.map(|i| format!("Name {i}\naddr{i}@example.org\n"))
|
||||
.collect::<Vec<String>>()
|
||||
.join("");
|
||||
|
||||
Contact::add_address_book(&context, &book).await.unwrap();
|
||||
|
||||
let query: Option<&str> = None;
|
||||
for _ in 0..read_count {
|
||||
Contact::get_all(&context, 0, query).await.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
fn criterion_benchmark(c: &mut Criterion) {
|
||||
let rt = tokio::runtime::Runtime::new().unwrap();
|
||||
|
||||
c.bench_function("create 500 contacts", |b| {
|
||||
b.to_async(&rt)
|
||||
.iter(|| async { address_book_benchmark(black_box(500), black_box(0)).await })
|
||||
});
|
||||
|
||||
c.bench_function("create 100 contacts and read it 1000 times", |b| {
|
||||
b.to_async(&rt)
|
||||
.iter(|| async { address_book_benchmark(black_box(100), black_box(1000)).await })
|
||||
});
|
||||
}
|
||||
|
||||
criterion_group!(benches, criterion_benchmark);
|
||||
criterion_main!(benches);
|
||||
@@ -1,30 +0,0 @@
|
||||
#![recursion_limit = "256"]
|
||||
use std::hint::black_box;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use criterion::{Criterion, criterion_group, criterion_main};
|
||||
use deltachat::accounts::Accounts;
|
||||
use tempfile::tempdir;
|
||||
|
||||
async fn create_accounts(n: u32) {
|
||||
let dir = tempdir().unwrap();
|
||||
let p: PathBuf = dir.path().join("accounts");
|
||||
|
||||
let writable = true;
|
||||
let mut accounts = Accounts::new(p.clone(), writable).await.unwrap();
|
||||
|
||||
for expected_id in 2..n {
|
||||
let id = accounts.add_account().await.unwrap();
|
||||
assert_eq!(id, expected_id);
|
||||
}
|
||||
}
|
||||
|
||||
fn criterion_benchmark(c: &mut Criterion) {
|
||||
c.bench_function("create 1 account", |b| {
|
||||
let rt = tokio::runtime::Runtime::new().unwrap();
|
||||
b.to_async(&rt).iter(|| create_accounts(black_box(1)))
|
||||
});
|
||||
}
|
||||
|
||||
criterion_group!(benches, criterion_benchmark);
|
||||
criterion_main!(benches);
|
||||
@@ -1,142 +0,0 @@
|
||||
//! Benchmarks for message decryption,
|
||||
//! comparing decryption of symmetrically-encrypted messages
|
||||
//! to decryption of asymmetrically-encrypted messages.
|
||||
//!
|
||||
//! Call with
|
||||
//!
|
||||
//! ```text
|
||||
//! cargo bench --bench decrypting --features="internals"
|
||||
//! ```
|
||||
//!
|
||||
//! or, if you want to only run e.g. the 'Decrypt and parse a symmetrically encrypted message' benchmark:
|
||||
//!
|
||||
//! ```text
|
||||
//! cargo bench --bench decrypting --features="internals" -- 'Decrypt and parse a symmetrically encrypted message'
|
||||
//! ```
|
||||
//!
|
||||
//! You can also pass a substring:
|
||||
//!
|
||||
//! ```text
|
||||
//! cargo bench --bench decrypting --features="internals" -- 'symmetrically'
|
||||
//! ```
|
||||
//!
|
||||
//! Symmetric decryption has to try out all known secrets,
|
||||
//! You can benchmark this by adapting the `NUM_SECRETS` variable.
|
||||
|
||||
use std::hint::black_box;
|
||||
use std::sync::LazyLock;
|
||||
|
||||
use criterion::{Criterion, criterion_group, criterion_main};
|
||||
use deltachat::internals_for_benches::create_broadcast_secret;
|
||||
use deltachat::internals_for_benches::save_broadcast_secret;
|
||||
use deltachat::securejoin::get_securejoin_qr;
|
||||
use deltachat::{
|
||||
Events, chat::ChatId, config::Config, context::Context, internals_for_benches::key_from_asc,
|
||||
internals_for_benches::parse_and_get_text, internals_for_benches::store_self_keypair,
|
||||
stock_str::StockStrings,
|
||||
};
|
||||
use tempfile::tempdir;
|
||||
|
||||
static NUM_BROADCAST_SECRETS: LazyLock<usize> = LazyLock::new(|| {
|
||||
std::env::var("NUM_BROADCAST_SECRETS")
|
||||
.unwrap_or("500".to_string())
|
||||
.parse()
|
||||
.unwrap()
|
||||
});
|
||||
static NUM_AUTH_TOKENS: LazyLock<usize> = LazyLock::new(|| {
|
||||
std::env::var("NUM_AUTH_TOKENS")
|
||||
.unwrap_or("5000".to_string())
|
||||
.parse()
|
||||
.unwrap()
|
||||
});
|
||||
|
||||
async fn create_context() -> Context {
|
||||
let dir = tempdir().unwrap();
|
||||
let dbfile = dir.path().join("db.sqlite");
|
||||
let context = Context::new(dbfile.as_path(), 100, Events::new(), StockStrings::new())
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
context
|
||||
.set_config(Config::ConfiguredAddr, Some("bob@example.net"))
|
||||
.await
|
||||
.unwrap();
|
||||
let secret = key_from_asc(include_str!("../test-data/key/bob-secret.asc")).unwrap();
|
||||
store_self_keypair(&context, &secret)
|
||||
.await
|
||||
.expect("Failed to save key");
|
||||
|
||||
context
|
||||
}
|
||||
|
||||
fn criterion_benchmark(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("Decrypt");
|
||||
|
||||
// ===========================================================================================
|
||||
// Benchmarks for the whole parsing pipeline, incl. decryption (but excl. receive_imf())
|
||||
// ===========================================================================================
|
||||
|
||||
let rt = tokio::runtime::Runtime::new().unwrap();
|
||||
let mut secrets = generate_secrets();
|
||||
|
||||
// "secret" is the shared secret that was used to encrypt text_symmetrically_encrypted.eml.
|
||||
// Put it into the middle of our secrets:
|
||||
secrets[*NUM_BROADCAST_SECRETS / 2] = "secret".to_string();
|
||||
|
||||
let context = rt.block_on(async {
|
||||
let context = create_context().await;
|
||||
for (i, secret) in secrets.iter().enumerate() {
|
||||
save_broadcast_secret(&context, ChatId::new(10 + i as u32), secret)
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
for _i in 0..*NUM_AUTH_TOKENS {
|
||||
get_securejoin_qr(&context, None).await.unwrap();
|
||||
}
|
||||
println!("NUM_AUTH_TOKENS={}", *NUM_AUTH_TOKENS);
|
||||
context
|
||||
});
|
||||
|
||||
group.bench_function("Decrypt and parse a symmetrically encrypted message", |b| {
|
||||
b.to_async(&rt).iter(|| {
|
||||
let ctx = context.clone();
|
||||
async move {
|
||||
let text = parse_and_get_text(
|
||||
&ctx,
|
||||
include_bytes!("../test-data/message/text_symmetrically_encrypted.eml"),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(black_box(text), "Symmetrically encrypted message");
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
group.bench_function("Decrypt and parse a public-key encrypted message", |b| {
|
||||
b.to_async(&rt).iter(|| {
|
||||
let ctx = context.clone();
|
||||
async move {
|
||||
let text = parse_and_get_text(
|
||||
&ctx,
|
||||
include_bytes!("../test-data/message/text_from_alice_encrypted.eml"),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(black_box(text), "hi");
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
group.finish();
|
||||
}
|
||||
|
||||
fn generate_secrets() -> Vec<String> {
|
||||
let secrets: Vec<String> = (0..*NUM_BROADCAST_SECRETS)
|
||||
.map(|_| create_broadcast_secret())
|
||||
.collect();
|
||||
println!("NUM_BROADCAST_SECRETS={}", *NUM_BROADCAST_SECRETS);
|
||||
secrets
|
||||
}
|
||||
|
||||
criterion_group!(benches, criterion_benchmark);
|
||||
criterion_main!(benches);
|
||||
@@ -1,48 +0,0 @@
|
||||
#![recursion_limit = "256"]
|
||||
use std::hint::black_box;
|
||||
use std::path::Path;
|
||||
|
||||
use criterion::{Criterion, criterion_group, criterion_main};
|
||||
use deltachat::Events;
|
||||
use deltachat::chat::{self, ChatId};
|
||||
use deltachat::chatlist::Chatlist;
|
||||
use deltachat::context::Context;
|
||||
use deltachat::stock_str::StockStrings;
|
||||
|
||||
async fn get_chat_msgs_benchmark(dbfile: &Path, chats: &[ChatId]) {
|
||||
let id = 100;
|
||||
let context = Context::new(dbfile, id, Events::new(), StockStrings::new())
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
for c in chats.iter().take(10) {
|
||||
black_box(chat::get_chat_msgs(&context, *c).await.ok());
|
||||
}
|
||||
}
|
||||
|
||||
fn criterion_benchmark(c: &mut Criterion) {
|
||||
// To enable this benchmark, set `DELTACHAT_BENCHMARK_DATABASE` to some large database with many
|
||||
// messages, such as your primary account.
|
||||
if let Ok(path) = std::env::var("DELTACHAT_BENCHMARK_DATABASE") {
|
||||
let rt = tokio::runtime::Runtime::new().unwrap();
|
||||
|
||||
let chats: Vec<_> = rt.block_on(async {
|
||||
let context = Context::new(Path::new(&path), 100, Events::new(), StockStrings::new())
|
||||
.await
|
||||
.unwrap();
|
||||
let chatlist = Chatlist::try_load(&context, 0, None, None).await.unwrap();
|
||||
let len = chatlist.len();
|
||||
(0..len).map(|i| chatlist.get_chat_id(i).unwrap()).collect()
|
||||
});
|
||||
|
||||
c.bench_function("chat::get_chat_msgs (load messages from 10 chats)", |b| {
|
||||
b.to_async(&rt)
|
||||
.iter(|| get_chat_msgs_benchmark(black_box(path.as_ref()), black_box(&chats)))
|
||||
});
|
||||
} else {
|
||||
println!("env var not set: DELTACHAT_BENCHMARK_DATABASE");
|
||||
}
|
||||
}
|
||||
|
||||
criterion_group!(benches, criterion_benchmark);
|
||||
criterion_main!(benches);
|
||||
@@ -1,35 +0,0 @@
|
||||
#![recursion_limit = "256"]
|
||||
use std::hint::black_box;
|
||||
use std::path::Path;
|
||||
|
||||
use criterion::{Criterion, criterion_group, criterion_main};
|
||||
use deltachat::Events;
|
||||
use deltachat::chatlist::Chatlist;
|
||||
use deltachat::context::Context;
|
||||
use deltachat::stock_str::StockStrings;
|
||||
|
||||
async fn get_chat_list_benchmark(context: &Context) {
|
||||
Chatlist::try_load(context, 0, None, None).await.unwrap();
|
||||
}
|
||||
|
||||
fn criterion_benchmark(c: &mut Criterion) {
|
||||
// To enable this benchmark, set `DELTACHAT_BENCHMARK_DATABASE` to some large database with many
|
||||
// messages, such as your primary account.
|
||||
if let Ok(path) = std::env::var("DELTACHAT_BENCHMARK_DATABASE") {
|
||||
let rt = tokio::runtime::Runtime::new().unwrap();
|
||||
let context = rt.block_on(async {
|
||||
Context::new(Path::new(&path), 100, Events::new(), StockStrings::new())
|
||||
.await
|
||||
.unwrap()
|
||||
});
|
||||
c.bench_function("chatlist:try_load (Get Chatlist)", |b| {
|
||||
b.to_async(&rt)
|
||||
.iter(|| get_chat_list_benchmark(black_box(&context)))
|
||||
});
|
||||
} else {
|
||||
println!("env var not set: DELTACHAT_BENCHMARK_DATABASE");
|
||||
}
|
||||
}
|
||||
|
||||
criterion_group!(benches, criterion_benchmark);
|
||||
criterion_main!(benches);
|
||||
@@ -1,95 +0,0 @@
|
||||
#![recursion_limit = "256"]
|
||||
use std::hint::black_box;
|
||||
use std::path::Path;
|
||||
|
||||
use criterion::{BatchSize, Criterion, criterion_group, criterion_main};
|
||||
use deltachat::Events;
|
||||
use deltachat::chat::{self, ChatId};
|
||||
use deltachat::chatlist::Chatlist;
|
||||
use deltachat::context::Context;
|
||||
use deltachat::stock_str::StockStrings;
|
||||
use futures_lite::future::block_on;
|
||||
use tempfile::tempdir;
|
||||
|
||||
async fn marknoticed_chat_benchmark(context: &Context, chats: &[ChatId]) {
|
||||
for c in chats.iter().take(20) {
|
||||
chat::marknoticed_chat(context, *c).await.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
fn criterion_benchmark(c: &mut Criterion) {
|
||||
// To enable this benchmark, set `DELTACHAT_BENCHMARK_DATABASE` to some large database with many
|
||||
// messages, such as your primary account.
|
||||
if let Ok(path) = std::env::var("DELTACHAT_BENCHMARK_DATABASE") {
|
||||
let rt = tokio::runtime::Runtime::new().unwrap();
|
||||
|
||||
let chats: Vec<_> = rt.block_on(async {
|
||||
let context = Context::new(Path::new(&path), 100, Events::new(), StockStrings::new())
|
||||
.await
|
||||
.unwrap();
|
||||
let chatlist = Chatlist::try_load(&context, 0, None, None).await.unwrap();
|
||||
let len = chatlist.len();
|
||||
(1..len).map(|i| chatlist.get_chat_id(i).unwrap()).collect()
|
||||
});
|
||||
|
||||
// This mainly tests the performance of marknoticed_chat()
|
||||
// when nothing has to be done
|
||||
c.bench_function(
|
||||
"chat::marknoticed_chat (mark 20 chats as noticed repeatedly)",
|
||||
|b| {
|
||||
let dir = tempdir().unwrap();
|
||||
let dir = dir.path();
|
||||
let new_db = dir.join("dc.db");
|
||||
std::fs::copy(&path, &new_db).unwrap();
|
||||
|
||||
let context = block_on(async {
|
||||
Context::new(Path::new(&new_db), 100, Events::new(), StockStrings::new())
|
||||
.await
|
||||
.unwrap()
|
||||
});
|
||||
|
||||
b.to_async(&rt)
|
||||
.iter(|| marknoticed_chat_benchmark(&context, black_box(&chats)))
|
||||
},
|
||||
);
|
||||
|
||||
// If the first 20 chats contain fresh messages or reactions,
|
||||
// this tests the performance of marking them as noticed.
|
||||
c.bench_function(
|
||||
"chat::marknoticed_chat (mark 20 chats as noticed, resetting after every iteration)",
|
||||
|b| {
|
||||
b.to_async(&rt).iter_batched(
|
||||
|| {
|
||||
let dir = tempdir().unwrap();
|
||||
let new_db = dir.path().join("dc.db");
|
||||
std::fs::copy(&path, &new_db).unwrap();
|
||||
|
||||
let context = block_on(async {
|
||||
Context::new(
|
||||
Path::new(&new_db),
|
||||
100,
|
||||
Events::new(),
|
||||
StockStrings::new(),
|
||||
)
|
||||
.await
|
||||
.unwrap()
|
||||
});
|
||||
(dir, context)
|
||||
},
|
||||
|(_dir, context)| {
|
||||
let chats = &chats;
|
||||
async move {
|
||||
marknoticed_chat_benchmark(black_box(&context), black_box(chats)).await
|
||||
}
|
||||
},
|
||||
BatchSize::PerIteration,
|
||||
);
|
||||
},
|
||||
);
|
||||
} else {
|
||||
println!("env var not set: DELTACHAT_BENCHMARK_DATABASE");
|
||||
}
|
||||
}
|
||||
|
||||
criterion_group!(benches, criterion_benchmark);
|
||||
criterion_main!(benches);
|
||||
@@ -1,160 +0,0 @@
|
||||
#![recursion_limit = "256"]
|
||||
use std::hint::black_box;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use criterion::{Criterion, criterion_group, criterion_main};
|
||||
use deltachat::{
|
||||
Events,
|
||||
config::Config,
|
||||
context::Context,
|
||||
imex::{ImexMode, imex},
|
||||
receive_imf::receive_imf,
|
||||
stock_str::StockStrings,
|
||||
};
|
||||
use tempfile::tempdir;
|
||||
|
||||
async fn recv_all_emails(context: Context, iteration: u32) -> Context {
|
||||
for i in 0..100 {
|
||||
let imf_raw = format!(
|
||||
"Subject: Benchmark
|
||||
Message-ID: Mr.{iteration}.{i}@testrun.org
|
||||
Date: Sat, 07 Dec 2019 19:00:27 +0000
|
||||
To: alice@example.com
|
||||
From: sender@testrun.org
|
||||
Chat-Version: 1.0
|
||||
Chat-Disposition-Notification-To: sender@testrun.org
|
||||
Chat-User-Avatar: 0
|
||||
In-Reply-To: Mr.{iteration}.{i_dec}@testrun.org
|
||||
MIME-Version: 1.0
|
||||
|
||||
Content-Type: text/plain; charset=utf-8; format=flowed; delsp=no
|
||||
|
||||
Hello {i}",
|
||||
i = i,
|
||||
i_dec = i - 1,
|
||||
);
|
||||
receive_imf(&context, black_box(imf_raw.as_bytes()), false)
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
context
|
||||
}
|
||||
|
||||
/// Receive 100 emails that remove charlie@example.com and add
|
||||
/// him back
|
||||
async fn recv_groupmembership_emails(context: Context, iteration: u32) -> Context {
|
||||
for i in 0..50 {
|
||||
let imf_raw = format!(
|
||||
"Subject: Benchmark
|
||||
Message-ID: Gr.{iteration}.ADD.{i}@testrun.org
|
||||
Date: Sat, 07 Dec 2019 19:00:27 +0000
|
||||
To: alice@example.com, b@example.com, c@example.com, d@example.com, e@example.com, f@example.com
|
||||
From: sender@testrun.org
|
||||
Chat-Version: 1.0
|
||||
Chat-Disposition-Notification-To: sender@testrun.org
|
||||
Chat-User-Avatar: 0
|
||||
Chat-Group-Member-Added: charlie@example.com
|
||||
In-Reply-To: Gr.{iteration}.REMOVE.{i_dec}@testrun.org
|
||||
MIME-Version: 1.0
|
||||
|
||||
Content-Type: text/plain; charset=utf-8; format=flowed; delsp=no
|
||||
|
||||
Hello {i}",
|
||||
i_dec = i - 1,
|
||||
);
|
||||
receive_imf(&context, black_box(imf_raw.as_bytes()), false)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let imf_raw = format!(
|
||||
"Subject: Benchmark
|
||||
Message-ID: Gr.{iteration}.REMOVE.{i}@testrun.org
|
||||
Date: Sat, 07 Dec 2019 19:00:27 +0000
|
||||
To: alice@example.com, b@example.com, c@example.com, d@example.com, e@example.com, f@example.com
|
||||
From: sender@testrun.org
|
||||
Chat-Version: 1.0
|
||||
Chat-Disposition-Notification-To: sender@testrun.org
|
||||
Chat-User-Avatar: 0
|
||||
Chat-Group-Member-Removed: charlie@example.com
|
||||
In-Reply-To: Gr.{iteration}.ADD.{i}@testrun.org
|
||||
MIME-Version: 1.0
|
||||
|
||||
Content-Type: text/plain; charset=utf-8; format=flowed; delsp=no
|
||||
|
||||
Hello {i}"
|
||||
);
|
||||
receive_imf(&context, black_box(imf_raw.as_bytes()), false)
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
context
|
||||
}
|
||||
|
||||
async fn create_context() -> Context {
|
||||
let dir = tempdir().unwrap();
|
||||
let dbfile = dir.path().join("db.sqlite");
|
||||
let id = 100;
|
||||
let context = Context::new(dbfile.as_path(), id, Events::new(), StockStrings::new())
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let backup: PathBuf = std::env::current_dir()
|
||||
.unwrap()
|
||||
.join("delta-chat-backup.tar");
|
||||
|
||||
if backup.exists() {
|
||||
println!("Importing backup");
|
||||
imex(&context, ImexMode::ImportBackup, backup.as_path(), None)
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
let addr = "alice@example.com";
|
||||
context.set_config(Config::Addr, Some(addr)).await.unwrap();
|
||||
context
|
||||
.set_config(Config::ConfiguredAddr, Some(addr))
|
||||
.await
|
||||
.unwrap();
|
||||
context
|
||||
.set_config(Config::Configured, Some("1"))
|
||||
.await
|
||||
.unwrap();
|
||||
context
|
||||
}
|
||||
|
||||
fn criterion_benchmark(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("Receive messages");
|
||||
group.bench_function("Receive 100 simple text msgs", |b| {
|
||||
let rt = tokio::runtime::Runtime::new().unwrap();
|
||||
let context = rt.block_on(create_context());
|
||||
let mut i = 0;
|
||||
|
||||
b.to_async(&rt).iter(|| {
|
||||
let ctx = context.clone();
|
||||
i += 1;
|
||||
async move {
|
||||
recv_all_emails(black_box(ctx), i).await;
|
||||
}
|
||||
});
|
||||
});
|
||||
group.bench_function(
|
||||
"Receive 100 Chat-Group-Member-{Added|Removed} messages",
|
||||
|b| {
|
||||
let rt = tokio::runtime::Runtime::new().unwrap();
|
||||
let context = rt.block_on(create_context());
|
||||
let mut i = 0;
|
||||
|
||||
b.to_async(&rt).iter(|| {
|
||||
let ctx = context.clone();
|
||||
i += 1;
|
||||
async move {
|
||||
recv_groupmembership_emails(black_box(ctx), i).await;
|
||||
}
|
||||
});
|
||||
},
|
||||
);
|
||||
group.finish();
|
||||
}
|
||||
|
||||
criterion_group!(benches, criterion_benchmark);
|
||||
criterion_main!(benches);
|
||||
@@ -1,36 +0,0 @@
|
||||
#![recursion_limit = "256"]
|
||||
use std::hint::black_box;
|
||||
use std::path::Path;
|
||||
|
||||
use criterion::{Criterion, criterion_group, criterion_main};
|
||||
use deltachat::Events;
|
||||
use deltachat::context::Context;
|
||||
use deltachat::stock_str::StockStrings;
|
||||
|
||||
async fn search_benchmark(dbfile: impl AsRef<Path>) {
|
||||
let id = 100;
|
||||
let context = Context::new(dbfile.as_ref(), id, Events::new(), StockStrings::new())
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
for _ in 0..10u32 {
|
||||
context.search_msgs(None, "hello").await.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
fn criterion_benchmark(c: &mut Criterion) {
|
||||
// To enable this benchmark, set `DELTACHAT_BENCHMARK_DATABASE` to some large database with many
|
||||
// messages, such as your primary account.
|
||||
if let Ok(path) = std::env::var("DELTACHAT_BENCHMARK_DATABASE") {
|
||||
let rt = tokio::runtime::Runtime::new().unwrap();
|
||||
|
||||
c.bench_function("search hello", |b| {
|
||||
b.to_async(&rt).iter(|| search_benchmark(black_box(&path)))
|
||||
});
|
||||
} else {
|
||||
println!("env var not set: DELTACHAT_BENCHMARK_DATABASE");
|
||||
}
|
||||
}
|
||||
|
||||
criterion_group!(benches, criterion_benchmark);
|
||||
criterion_main!(benches);
|
||||
@@ -1,48 +0,0 @@
|
||||
#![recursion_limit = "256"]
|
||||
use criterion::{Criterion, criterion_group, criterion_main};
|
||||
|
||||
use deltachat::context::Context;
|
||||
use deltachat::stock_str::StockStrings;
|
||||
use deltachat::{Event, EventType, Events};
|
||||
use tempfile::tempdir;
|
||||
|
||||
async fn send_events_benchmark(context: &Context) {
|
||||
let emitter = context.get_event_emitter();
|
||||
for _i in 0..1_000_000 {
|
||||
context.emit_event(EventType::Info("interesting event...".to_string()));
|
||||
}
|
||||
context.emit_event(EventType::Info("DONE".to_string()));
|
||||
|
||||
loop {
|
||||
match emitter.recv().await.unwrap() {
|
||||
Event {
|
||||
typ: EventType::Info(info),
|
||||
..
|
||||
} if info.contains("DONE") => {
|
||||
break;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn criterion_benchmark(c: &mut Criterion) {
|
||||
let dir = tempdir().unwrap();
|
||||
let dbfile = dir.path().join("db.sqlite");
|
||||
let rt = tokio::runtime::Runtime::new().unwrap();
|
||||
|
||||
let context = rt.block_on(async {
|
||||
Context::new(&dbfile, 100, Events::new(), StockStrings::new())
|
||||
.await
|
||||
.expect("failed to create context")
|
||||
});
|
||||
let executor = tokio::runtime::Runtime::new().unwrap();
|
||||
|
||||
c.bench_function("Sending 1.000.000 events", |b| {
|
||||
b.to_async(&executor)
|
||||
.iter(|| send_events_benchmark(&context))
|
||||
});
|
||||
}
|
||||
|
||||
criterion_group!(benches, criterion_benchmark);
|
||||
criterion_main!(benches);
|
||||
46
ci_scripts/README.md
Normal file
@@ -0,0 +1,46 @@
|
||||
# Continuous Integration Scripts for Delta Chat
|
||||
|
||||
Continuous Integration, run through CircleCI and an own build machine.
|
||||
|
||||
## Description of scripts
|
||||
|
||||
- `../.circleci/config.yml` describing the build jobs that are run
|
||||
by Circle-CI
|
||||
|
||||
- `remote_tests_python.sh` rsyncs to a build machine and runs
|
||||
`run-python-test.sh` remotely on the build machine.
|
||||
|
||||
- `remote_tests_rust.sh` rsyncs to the build machine and runs
|
||||
`run-rust-test.sh` remotely on the build machine.
|
||||
|
||||
- `doxygen/Dockerfile` specifies an image that contains
|
||||
the doxygen tool which is used by `run-doxygen.sh`
|
||||
to generate C-docs which are then uploaded
|
||||
via `ci_upload.sh` to `https://c.delta.chat/_unofficial_unreleased_docs/<BRANCH>`
|
||||
(and the master branch is linked to https://c.delta.chat proper).
|
||||
|
||||
|
||||
## Triggering runs on the build machine locally (fast!)
|
||||
|
||||
There is experimental support for triggering a remote Python or Rust test run
|
||||
from your local checkout/branch. You will need to be authorized to login to
|
||||
the build machine (ask your friendly sysadmin on #deltachat freenode) to type::
|
||||
|
||||
ci_scripts/manual_remote_tests.sh rust
|
||||
ci_scripts/manual_remote_tests.sh python
|
||||
|
||||
This will **rsync** your current checkout to the remote build machine
|
||||
(no need to commit before) and then run either rust or python tests.
|
||||
|
||||
# Outdated files (for later re-use)
|
||||
|
||||
`coredeps/Dockerfile` specifies an image that contains all
|
||||
of Delta Chat's core dependencies. It used to run
|
||||
python tests and build wheels (binary packages for Python)
|
||||
|
||||
You can build the docker images yourself locally
|
||||
to avoid the relatively large download::
|
||||
|
||||
cd ci_scripts # where all CI things are
|
||||
docker build -t deltachat/coredeps docker-coredeps
|
||||
docker build -t deltachat/doxygen docker-doxygen
|
||||
55
ci_scripts/ci_upload.sh
Executable file
@@ -0,0 +1,55 @@
|
||||
#!/bin/bash
|
||||
|
||||
if [ -z "$DEVPI_LOGIN" ] ; then
|
||||
echo "required: password for 'dc' user on https://m.devpi/net/dc index"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
set -xe
|
||||
|
||||
PYDOCDIR=${1:?directory with python docs}
|
||||
WHEELHOUSEDIR=${2:?directory with pre-built wheels}
|
||||
DOXYDOCDIR=${3:?directory where doxygen docs to be found}
|
||||
|
||||
export BRANCH=${CIRCLE_BRANCH:?specify branch for uploading purposes}
|
||||
|
||||
|
||||
# DISABLED: python docs to py.delta.chat
|
||||
#ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null delta@py.delta.chat mkdir -p build/${BRANCH}
|
||||
#rsync -avz \
|
||||
# -e "ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null" \
|
||||
# "$PYDOCDIR/html/" \
|
||||
# delta@py.delta.chat:build/${BRANCH}
|
||||
|
||||
# C docs to c.delta.chat
|
||||
ssh -o BatchMode=yes -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null delta@c.delta.chat mkdir -p build-c/${BRANCH}
|
||||
rsync -avz \
|
||||
-e "ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null" \
|
||||
"$DOXYDOCDIR/html/" \
|
||||
delta@c.delta.chat:build-c/${BRANCH}
|
||||
|
||||
exit 0
|
||||
|
||||
# OUTDATED -- for re-use from python release-scripts
|
||||
|
||||
echo -----------------------
|
||||
echo upload wheels
|
||||
echo -----------------------
|
||||
|
||||
# Bundle external shared libraries into the wheels
|
||||
pushd $WHEELHOUSEDIR
|
||||
|
||||
pip3 install devpi-client
|
||||
devpi use https://m.devpi.net
|
||||
devpi login dc --password $DEVPI_LOGIN
|
||||
|
||||
N_BRANCH=${BRANCH//[\/]}
|
||||
|
||||
devpi use dc/$N_BRANCH || {
|
||||
devpi index -c $N_BRANCH
|
||||
devpi use dc/$N_BRANCH
|
||||
}
|
||||
devpi index $N_BRANCH bases=/root/pypi
|
||||
devpi upload deltachat*.whl
|
||||
|
||||
popd
|
||||
24
ci_scripts/docker-coredeps/Dockerfile
Normal file
@@ -0,0 +1,24 @@
|
||||
FROM quay.io/pypa/manylinux1_x86_64
|
||||
|
||||
# Configure ld.so/ldconfig and pkg-config
|
||||
RUN echo /usr/local/lib64 > /etc/ld.so.conf.d/local.conf && \
|
||||
echo /usr/local/lib >> /etc/ld.so.conf.d/local.conf
|
||||
ENV PKG_CONFIG_PATH /usr/local/lib64/pkgconfig:/usr/local/lib/pkgconfig
|
||||
|
||||
# Install a recent Perl, needed to install OpenSSL
|
||||
ADD deps/build_perl.sh /builder/build_perl.sh
|
||||
RUN mkdir tmp1 && cd tmp1 && bash /builder/build_perl.sh && cd .. && rm -r tmp1
|
||||
|
||||
# Install OpenSSL
|
||||
ADD deps/build_openssl.sh /builder/build_openssl.sh
|
||||
RUN mkdir tmp1 && cd tmp1 && bash /builder/build_openssl.sh && cd .. && rm -r tmp1
|
||||
|
||||
ENV PIP_DISABLE_PIP_VERSION_CHECK 1
|
||||
|
||||
# Install python tools (auditwheels,tox, ...)
|
||||
ADD deps/build_python.sh /builder/build_python.sh
|
||||
RUN mkdir tmp1 && cd tmp1 && bash /builder/build_python.sh && cd .. && rm -r tmp1
|
||||
|
||||
# Install Rust nightly
|
||||
ADD deps/build_rust.sh /builder/build_rust.sh
|
||||
RUN mkdir tmp1 && cd tmp1 && bash /builder/build_rust.sh && cd .. && rm -r tmp1
|
||||
21
ci_scripts/docker-coredeps/deps/build_openssl.sh
Executable file
@@ -0,0 +1,21 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e -x
|
||||
|
||||
OPENSSL_VERSION=1.1.1a
|
||||
OPENSSL_SHA256=fc20130f8b7cbd2fb918b2f14e2f429e109c31ddd0fb38fc5d71d9ffed3f9f41
|
||||
|
||||
curl -O https://www.openssl.org/source/openssl-${OPENSSL_VERSION}.tar.gz
|
||||
echo "${OPENSSL_SHA256} openssl-${OPENSSL_VERSION}.tar.gz" | sha256sum -c -
|
||||
tar xzf openssl-${OPENSSL_VERSION}.tar.gz
|
||||
cd openssl-${OPENSSL_VERSION}
|
||||
./config shared no-ssl2 no-ssl3 -fPIC --prefix=/usr/local
|
||||
|
||||
sed -i "s/^SHLIB_MAJOR=.*/SHLIB_MAJOR=200/" Makefile && \
|
||||
sed -i "s/^SHLIB_MINOR=.*/SHLIB_MINOR=0.0/" Makefile && \
|
||||
sed -i "s/^SHLIB_VERSION_NUMBER=.*/SHLIB_VERSION_NUMBER=200.0.0/" Makefile
|
||||
|
||||
make depend
|
||||
make
|
||||
make install_sw install_ssldirs
|
||||
ldconfig -v | grep ssl
|
||||
12
ci_scripts/docker-coredeps/deps/build_perl.sh
Executable file
@@ -0,0 +1,12 @@
|
||||
#!/bin/bash
|
||||
|
||||
PERL_VERSION=5.30.0
|
||||
# PERL_SHA256=7e929f64d4cb0e9d1159d4a59fc89394e27fa1f7004d0836ca0d514685406ea8
|
||||
curl -O https://www.cpan.org/src/5.0/perl-${PERL_VERSION}.tar.gz
|
||||
# echo "${PERL_SHA256} perl-${PERL_VERSION}.tar.gz" | sha256sum -c -
|
||||
tar -xzf perl-${PERL_VERSION}.tar.gz
|
||||
cd perl-${PERL_VERSION}
|
||||
|
||||
./Configure -de
|
||||
make
|
||||
make install
|
||||
14
ci_scripts/docker-coredeps/deps/build_python.sh
Executable file
@@ -0,0 +1,14 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -x -e
|
||||
|
||||
# we use the python3.5 environment as the base environment
|
||||
/opt/python/cp35-cp35m/bin/pip install tox devpi-client auditwheel
|
||||
|
||||
pushd /usr/bin
|
||||
|
||||
ln -s /opt/_internal/cpython-3.5.*/bin/tox
|
||||
ln -s /opt/_internal/cpython-3.5.*/bin/devpi
|
||||
ln -s /opt/_internal/cpython-3.5.*/bin/auditwheel
|
||||
|
||||
popd
|
||||
8
ci_scripts/docker-coredeps/deps/build_rust.sh
Executable file
@@ -0,0 +1,8 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e -x
|
||||
|
||||
# Install Rust
|
||||
curl https://sh.rustup.rs -sSf | sh -s -- --default-toolchain nightly-2019-09-12 -y
|
||||
export PATH=/root/.cargo/bin:$PATH
|
||||
rustc --version
|
||||
49
ci_scripts/docker-coredeps/deps/run_all.sh
Executable file
@@ -0,0 +1,49 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# Build the Delta Chat C/Rust library
|
||||
#
|
||||
set -e -x
|
||||
|
||||
# perform clean build of core and install
|
||||
export TOXWORKDIR=.docker-tox
|
||||
|
||||
# build core library
|
||||
|
||||
cargo build --release -p deltachat_ffi
|
||||
|
||||
# configure access to a base python and
|
||||
# to several python interpreters needed by tox below
|
||||
export PATH=$PATH:/opt/python/cp35-cp35m/bin
|
||||
export PYTHONDONTWRITEBYTECODE=1
|
||||
pushd /bin
|
||||
ln -s /opt/python/cp27-cp27m/bin/python2.7
|
||||
ln -s /opt/python/cp36-cp36m/bin/python3.6
|
||||
ln -s /opt/python/cp37-cp37m/bin/python3.7
|
||||
popd
|
||||
|
||||
#
|
||||
# run python tests
|
||||
#
|
||||
|
||||
if [ -n "$TESTS" ]; then
|
||||
|
||||
echo ----------------
|
||||
echo run python tests
|
||||
echo ----------------
|
||||
|
||||
pushd python
|
||||
# first run all tests ...
|
||||
rm -rf tests/__pycache__
|
||||
rm -rf src/deltachat/__pycache__
|
||||
export PYTHONDONTWRITEBYTECODE=1
|
||||
tox --workdir "$TOXWORKDIR" -e py27,py35,py36,py37
|
||||
popd
|
||||
fi
|
||||
|
||||
|
||||
if [ -n "$DOCS" ]; then
|
||||
echo -----------------------
|
||||
echo generating python docs
|
||||
echo -----------------------
|
||||
(cd python && tox --workdir "$TOXWORKDIR" -e doc)
|
||||
fi
|
||||
5
ci_scripts/docker-doxygen/Dockerfile
Normal file
@@ -0,0 +1,5 @@
|
||||
FROM debian:stable
|
||||
|
||||
# this is tagged as deltachat/doxygen
|
||||
|
||||
RUN apt-get update && apt-get install -y doxygen
|
||||
9
ci_scripts/manual_remote_tests.sh
Executable file
@@ -0,0 +1,9 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -xe
|
||||
export CIRCLE_JOB=remote_tests_${1:?need to specify 'rust' or 'python'}
|
||||
export CIRCLE_BUILD_NUM=$USER
|
||||
export CIRCLE_BRANCH=`git branch | grep \* | cut -d ' ' -f2`
|
||||
export CIRCLE_PROJECT_REPONAME=$(basename `git rev-parse --show-toplevel`)
|
||||
|
||||
time bash ci_scripts/$CIRCLE_JOB.sh
|
||||
77
ci_scripts/old/gh-actions-rust.yml
Normal file
@@ -0,0 +1,77 @@
|
||||
name: CI
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
|
||||
env:
|
||||
RUSTFLAGS: -Dwarnings
|
||||
|
||||
jobs:
|
||||
build_and_test:
|
||||
name: Build and test
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, windows-latest, macOS-latest]
|
||||
rust: [nightly]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@master
|
||||
|
||||
- name: Install ${{ matrix.rust }}
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: ${{ matrix.rust }}
|
||||
override: true
|
||||
|
||||
- name: check
|
||||
uses: actions-rs/cargo@v1
|
||||
if: matrix.rust == 'nightly'
|
||||
with:
|
||||
command: check
|
||||
args: --all --bins --examples --tests
|
||||
|
||||
- name: tests
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: test
|
||||
args: --all
|
||||
|
||||
- name: tests ignored
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: test
|
||||
args: --all --release -- --ignored
|
||||
|
||||
check_fmt:
|
||||
name: Checking fmt and docs
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@master
|
||||
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: nightly
|
||||
override: true
|
||||
components: rustfmt
|
||||
|
||||
- name: fmt
|
||||
run: cargo fmt --all -- --check
|
||||
|
||||
# clippy_check:
|
||||
# name: Clippy check
|
||||
# runs-on: ubuntu-latest
|
||||
#
|
||||
# steps:
|
||||
# - uses: actions/checkout@v1
|
||||
# - uses: actions-rs/toolchain@v1
|
||||
# with:
|
||||
# profile: minimal
|
||||
# toolchain: nightly
|
||||
# override: true
|
||||
# components: clippy
|
||||
#
|
||||
# - name: clippy
|
||||
# run: cargo clippy --all
|
||||
60
ci_scripts/old/run-python.sh
Executable file
@@ -0,0 +1,60 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# Build the Delta Chat C/Rust library typically run in a docker
|
||||
# container that contains all library deps but should also work
|
||||
# outside if you have the dependencies installed on your system.
|
||||
|
||||
set -e -x
|
||||
|
||||
# Perform clean build of core and install.
|
||||
export TOXWORKDIR=.docker-tox
|
||||
|
||||
# install core lib
|
||||
|
||||
export PATH=/root/.cargo/bin:$PATH
|
||||
cargo build --release -p deltachat_ffi
|
||||
# cargo test --all --all-features
|
||||
|
||||
# Statically link against libdeltachat.a.
|
||||
export DCC_RS_DEV=$(pwd)
|
||||
|
||||
# Configure access to a base python and to several python interpreters
|
||||
# needed by tox below.
|
||||
export PATH=$PATH:/opt/python/cp35-cp35m/bin
|
||||
export PYTHONDONTWRITEBYTECODE=1
|
||||
pushd /bin
|
||||
ln -s /opt/python/cp27-cp27m/bin/python2.7
|
||||
ln -s /opt/python/cp36-cp36m/bin/python3.6
|
||||
ln -s /opt/python/cp37-cp37m/bin/python3.7
|
||||
popd
|
||||
|
||||
if [ -n "$TESTS" ]; then
|
||||
|
||||
pushd python
|
||||
# prepare a clean tox run
|
||||
rm -rf tests/__pycache__
|
||||
rm -rf src/deltachat/__pycache__
|
||||
export PYTHONDONTWRITEBYTECODE=1
|
||||
|
||||
# run tox. The circle-ci project env-var-setting DCC_PY_LIVECONFIG
|
||||
# allows running of "liveconfig" tests but for speed reasons
|
||||
# we run them only for the highest python version we support
|
||||
|
||||
# we split out qr-tests run to minimize likelyness of flaky tests
|
||||
# (some qr tests are pretty heavy in terms of send/received
|
||||
# messages and rust's imap code likely has concurrency problems)
|
||||
tox --workdir "$TOXWORKDIR" -e py37 -- --reruns 3 -k "not qr"
|
||||
tox --workdir "$TOXWORKDIR" -e py37 -- --reruns 3 -k "qr"
|
||||
unset DCC_PY_LIVECONFIG
|
||||
tox --workdir "$TOXWORKDIR" -p4 -e lint,py35,py36,doc
|
||||
tox --workdir "$TOXWORKDIR" -e auditwheels
|
||||
popd
|
||||
fi
|
||||
|
||||
|
||||
# if [ -n "$DOCS" ]; then
|
||||
# echo -----------------------
|
||||
# echo generating python docs
|
||||
# echo -----------------------
|
||||
# (cd python && tox --workdir "$TOXWORKDIR" -e doc)
|
||||
# fi
|
||||
46
ci_scripts/remote_tests_python.sh
Executable file
@@ -0,0 +1,46 @@
|
||||
#!/bin/bash
|
||||
|
||||
export BRANCH=${CIRCLE_BRANCH:?branch to build}
|
||||
export REPONAME=${CIRCLE_PROJECT_REPONAME:?repository name}
|
||||
export SSHTARGET=ci@b1.delta.chat
|
||||
|
||||
# we construct the BUILDDIR such that we can easily share the
|
||||
# CARGO_TARGET_DIR between runs ("..")
|
||||
export BUILDDIR=ci_builds/$REPONAME/$BRANCH/${CIRCLE_JOB:?jobname}/${CIRCLE_BUILD_NUM:?circle-build-number}
|
||||
|
||||
echo "--- Copying files to $SSHTARGET:$BUILDDIR"
|
||||
|
||||
set -xe
|
||||
|
||||
ssh -oBatchMode=yes -oStrictHostKeyChecking=no $SSHTARGET mkdir -p "$BUILDDIR"
|
||||
git ls-files >.rsynclist
|
||||
# we seem to need .git for setuptools_scm versioning
|
||||
find .git >>.rsynclist
|
||||
rsync --delete --files-from=.rsynclist -az ./ "$SSHTARGET:$BUILDDIR"
|
||||
|
||||
set +x
|
||||
|
||||
echo "--- Running $CIRCLE_JOB remotely"
|
||||
|
||||
ssh $SSHTARGET <<_HERE
|
||||
set +x -e
|
||||
cd $BUILDDIR
|
||||
# let's share the target dir with our last run on this branch/job-type
|
||||
# cargo will make sure to block/unblock us properly
|
||||
export CARGO_TARGET_DIR=\`pwd\`/../target
|
||||
export TARGET=release
|
||||
export DCC_PY_LIVECONFIG=$DCC_PY_LIVECONFIG
|
||||
|
||||
#we rely on tox/virtualenv being available in the host
|
||||
#rm -rf virtualenv venv
|
||||
#virtualenv -q -p python3.7 venv
|
||||
#source venv/bin/activate
|
||||
#pip install -q tox virtualenv
|
||||
|
||||
set -x
|
||||
which python
|
||||
source \$HOME/venv/bin/activate
|
||||
which python
|
||||
|
||||
bash ci_scripts/run-python-test.sh
|
||||
_HERE
|
||||
32
ci_scripts/remote_tests_rust.sh
Executable file
@@ -0,0 +1,32 @@
|
||||
#!/bin/bash
|
||||
|
||||
export BRANCH=${CIRCLE_BRANCH:?branch to build}
|
||||
export REPONAME=${CIRCLE_PROJECT_REPONAME:?repository name}
|
||||
export SSHTARGET=ci@b1.delta.chat
|
||||
|
||||
# we construct the BUILDDIR such that we can easily share the
|
||||
# CARGO_TARGET_DIR between runs ("..")
|
||||
export BUILDDIR=ci_builds/$REPONAME/$BRANCH/${CIRCLE_JOB:?jobname}/${CIRCLE_BUILD_NUM:?circle-build-number}
|
||||
|
||||
set -e
|
||||
|
||||
echo "--- Copying files to $SSHTARGET:$BUILDDIR"
|
||||
|
||||
ssh -oBatchMode=yes -oStrictHostKeyChecking=no $SSHTARGET mkdir -p "$BUILDDIR"
|
||||
git ls-files >.rsynclist
|
||||
rsync --delete --files-from=.rsynclist -az ./ "$SSHTARGET:$BUILDDIR"
|
||||
|
||||
echo "--- Running $CIRCLE_JOB remotely"
|
||||
|
||||
ssh $SSHTARGET <<_HERE
|
||||
set +x -e
|
||||
cd $BUILDDIR
|
||||
# let's share the target dir with our last run on this branch/job-type
|
||||
# cargo will make sure to block/unblock us properly
|
||||
export CARGO_TARGET_DIR=\`pwd\`/../target
|
||||
export TARGET=x86_64-unknown-linux-gnu
|
||||
export RUSTC_WRAPPER=sccache
|
||||
|
||||
bash ci_scripts/run-rust-test.sh
|
||||
_HERE
|
||||
|
||||
7
ci_scripts/run-doxygen.sh
Executable file
@@ -0,0 +1,7 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -ex
|
||||
|
||||
cd deltachat-ffi
|
||||
doxygen
|
||||
|
||||
@@ -4,15 +4,14 @@
|
||||
# and tox/pytest.
|
||||
|
||||
set -e -x
|
||||
shopt -s huponexit
|
||||
|
||||
# for core-building and python install step
|
||||
export DCC_RS_TARGET=debug
|
||||
export DCC_RS_TARGET=release
|
||||
export DCC_RS_DEV=`pwd`
|
||||
|
||||
cd python
|
||||
|
||||
cargo build -p deltachat_ffi
|
||||
python install_python_bindings.py onlybuild
|
||||
|
||||
# remove and inhibit writing PYC files
|
||||
rm -rf tests/__pycache__
|
||||
@@ -22,5 +21,4 @@ export PYTHONDONTWRITEBYTECODE=1
|
||||
# run python tests (tox invokes pytest to run tests in python/tests)
|
||||
#TOX_PARALLEL_NO_SPINNER=1 tox -e lint,doc
|
||||
tox -e lint
|
||||
tox -e doc
|
||||
tox -e py -- "$@"
|
||||
tox -e doc,py37
|
||||
@@ -1,7 +1,6 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -ex
|
||||
shopt -s huponexit
|
||||
|
||||
#export RUST_TEST_THREADS=1
|
||||
export RUST_BACKTRACE=1
|
||||
93
cliff.toml
@@ -1,93 +0,0 @@
|
||||
# configuration file for git-cliff
|
||||
# see https://git-cliff.org/docs/configuration/
|
||||
|
||||
|
||||
[git]
|
||||
# parse the commits based on https://www.conventionalcommits.org
|
||||
conventional_commits = true
|
||||
# filter out the commits that are not conventional
|
||||
filter_unconventional = false
|
||||
# process each line of a commit as an individual commit
|
||||
split_commits = false
|
||||
# regex for preprocessing the commit messages
|
||||
commit_preprocessors = [
|
||||
{ pattern = '\((\w+\s)?#([0-9]+)\)', replace = "([#${2}](https://github.com/chatmail/core/pull/${2}))"}, # replace pull request / issue numbers
|
||||
]
|
||||
# regex for parsing and grouping commits
|
||||
commit_parsers = [
|
||||
{ message = "^feat", group = "Features / Changes"},
|
||||
{ message = "^fix", group = "Fixes"},
|
||||
{ message = "^api", group = "API-Changes" },
|
||||
{ message = "^refactor", group = "Refactor"},
|
||||
{ message = "^perf", group = "Performance"},
|
||||
{ message = "^test", group = "Tests"},
|
||||
{ message = "^style", group = "Styling"},
|
||||
{ message = "^chore\\(release\\): prepare for", skip = true},
|
||||
{ message = "^chore", group = "Miscellaneous Tasks"},
|
||||
{ message = "^build", group = "Build system"},
|
||||
{ message = "^docs", group = "Documentation"},
|
||||
{ message = "^ci", group = "CI"},
|
||||
{ message = ".*", group = "Other"},
|
||||
# { body = ".*security", group = "Security"},
|
||||
]
|
||||
# protect breaking changes from being skipped due to matching a skipping commit_parser
|
||||
protect_breaking_commits = true
|
||||
# filter out the commits that are not matched by commit parsers
|
||||
filter_commits = true
|
||||
# glob pattern for matching git tags
|
||||
tag_pattern = "v[0-9]*"
|
||||
# regex for skipping tags
|
||||
#skip_tags = "v0.1.0-beta.1"
|
||||
# regex for ignoring tags
|
||||
ignore_tags = ""
|
||||
# sort the tags topologically
|
||||
topo_order = false
|
||||
# sort the commits inside sections by oldest/newest order
|
||||
sort_commits = "oldest"
|
||||
# limit the number of commits included in the changelog.
|
||||
# limit_commits = 42
|
||||
|
||||
|
||||
[changelog]
|
||||
# changelog header
|
||||
header = """
|
||||
# Changelog\n
|
||||
"""
|
||||
# template for the changelog body
|
||||
# https://keats.github.io/tera/docs/#templates
|
||||
body = """
|
||||
{% if version %}\
|
||||
## [{{ version | trim_start_matches(pat="v") }}] - {{ timestamp | date(format="%Y-%m-%d") }}
|
||||
{% else %}\
|
||||
## [unreleased]
|
||||
{% endif %}\
|
||||
{% for group, commits in commits | group_by(attribute="group") %}
|
||||
### {{ group | upper_first }}
|
||||
{% for commit in commits %}
|
||||
- {% if commit.breaking %}[**breaking**] {% endif %}\
|
||||
{% if commit.scope %}{{ commit.scope }}: {% endif %}\
|
||||
{{ commit.message }}.\
|
||||
{% if commit.footers is defined %}\
|
||||
{% for footer in commit.footers %}{% if 'BREAKING CHANGE' in footer.token %}
|
||||
{% raw %} {% endraw %}- {{ footer.value }}\
|
||||
{% endif %}{% endfor %}\
|
||||
{% endif%}\
|
||||
{% endfor %}
|
||||
{% endfor %}\n
|
||||
"""
|
||||
# remove the leading and trailing whitespace from the template
|
||||
trim = true
|
||||
footer = """
|
||||
{% for release in releases -%}
|
||||
{% if release.version -%}
|
||||
{% if release.previous.version -%}
|
||||
[{{ release.version | trim_start_matches(pat="v") }}]: \
|
||||
https://github.com/chatmail/core\
|
||||
/compare/{{ release.previous.version }}..{{ release.version }}
|
||||
{% endif -%}
|
||||
{% else -%}
|
||||
[unreleased]: https://github.com/chatmail/core\
|
||||
/compare/{{ release.previous.version }}..HEAD
|
||||
{% endif -%}
|
||||
{% endfor %}
|
||||
"""
|
||||
@@ -1,17 +0,0 @@
|
||||
[package]
|
||||
name = "deltachat-contact-tools"
|
||||
version = "0.0.0" # No semver-stable versioning
|
||||
edition = "2021"
|
||||
description = "Contact-related tools, like parsing vcards and sanitizing name and address. Meant for internal use in the deltachat crate."
|
||||
license = "MPL-2.0"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
anyhow = { workspace = true }
|
||||
regex = { workspace = true }
|
||||
rusqlite = { workspace = true } # Needed in order to `impl rusqlite::types::ToSql for EmailAddress`. Could easily be put behind a feature.
|
||||
chrono = { workspace = true, features = ["alloc", "clock", "std"] }
|
||||
|
||||
[dev-dependencies]
|
||||
anyhow = { workspace = true, features = ["backtrace"] } # Enable `backtrace` feature in tests.
|
||||
@@ -1,401 +0,0 @@
|
||||
//! Contact-related tools, like parsing vcards and sanitizing name and address
|
||||
|
||||
#![forbid(unsafe_code)]
|
||||
#![warn(
|
||||
unused,
|
||||
clippy::correctness,
|
||||
missing_debug_implementations,
|
||||
missing_docs,
|
||||
clippy::all,
|
||||
clippy::wildcard_imports,
|
||||
clippy::needless_borrow,
|
||||
clippy::cast_lossless,
|
||||
clippy::unused_async,
|
||||
clippy::explicit_iter_loop,
|
||||
clippy::explicit_into_iter_loop,
|
||||
clippy::cloned_instead_of_copied
|
||||
)]
|
||||
#![cfg_attr(not(test), forbid(clippy::indexing_slicing))]
|
||||
#![cfg_attr(not(test), forbid(clippy::string_slice))]
|
||||
#![allow(
|
||||
clippy::match_bool,
|
||||
clippy::mixed_read_write_in_expression,
|
||||
clippy::bool_assert_comparison,
|
||||
clippy::manual_split_once,
|
||||
clippy::format_push_string,
|
||||
clippy::bool_to_int_with_if,
|
||||
clippy::manual_range_contains
|
||||
)]
|
||||
|
||||
use std::fmt;
|
||||
use std::ops::Deref;
|
||||
use std::sync::LazyLock;
|
||||
|
||||
use anyhow::bail;
|
||||
use anyhow::Result;
|
||||
use regex::Regex;
|
||||
|
||||
mod vcard;
|
||||
pub use vcard::{make_vcard, parse_vcard, VcardContact};
|
||||
|
||||
/// Valid contact address.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ContactAddress(String);
|
||||
|
||||
impl Deref for ContactAddress {
|
||||
type Target = str;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<str> for ContactAddress {
|
||||
fn as_ref(&self) -> &str {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for ContactAddress {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl ContactAddress {
|
||||
/// Constructs a new contact address from string,
|
||||
/// normalizing and validating it.
|
||||
pub fn new(s: &str) -> Result<Self> {
|
||||
let addr = addr_normalize(s);
|
||||
if !may_be_valid_addr(&addr) {
|
||||
bail!("invalid address {s:?}");
|
||||
}
|
||||
Ok(Self(addr.to_string()))
|
||||
}
|
||||
}
|
||||
|
||||
/// Allow converting [`ContactAddress`] to an SQLite type.
|
||||
impl rusqlite::types::ToSql for ContactAddress {
|
||||
fn to_sql(&self) -> rusqlite::Result<rusqlite::types::ToSqlOutput<'_>> {
|
||||
let val = rusqlite::types::Value::Text(self.0.to_string());
|
||||
let out = rusqlite::types::ToSqlOutput::Owned(val);
|
||||
Ok(out)
|
||||
}
|
||||
}
|
||||
|
||||
/// Takes a name and an address and sanitizes them:
|
||||
/// - Extracts a name from the addr if the addr is in form "Alice <alice@example.org>"
|
||||
/// - Removes special characters from the name, see [`sanitize_name()`]
|
||||
/// - Removes the name if it is equal to the address by setting it to ""
|
||||
pub fn sanitize_name_and_addr(name: &str, addr: &str) -> (String, String) {
|
||||
static ADDR_WITH_NAME_REGEX: LazyLock<Regex> =
|
||||
LazyLock::new(|| Regex::new("(.*)<(.*)>").unwrap());
|
||||
let (name, addr) = if let Some(captures) = ADDR_WITH_NAME_REGEX.captures(addr.as_ref()) {
|
||||
(
|
||||
if name.is_empty() {
|
||||
captures.get(1).map_or("", |m| m.as_str())
|
||||
} else {
|
||||
name
|
||||
},
|
||||
captures
|
||||
.get(2)
|
||||
.map_or("".to_string(), |m| m.as_str().to_string()),
|
||||
)
|
||||
} else {
|
||||
(name, addr.to_string())
|
||||
};
|
||||
let mut name = sanitize_name(name);
|
||||
|
||||
// If the 'display name' is just the address, remove it:
|
||||
// Otherwise, the contact would sometimes be shown as "alice@example.com (alice@example.com)" (see `get_name_n_addr()`).
|
||||
// If the display name is empty, DC will just show the address when it needs a display name.
|
||||
if name == addr {
|
||||
name = "".to_string();
|
||||
}
|
||||
|
||||
(name, addr)
|
||||
}
|
||||
|
||||
/// Sanitizes a name.
|
||||
///
|
||||
/// - Removes newlines and trims the string
|
||||
/// - Removes quotes (come from some bad MUA implementations)
|
||||
/// - Removes potentially-malicious bidi characters
|
||||
pub fn sanitize_name(name: &str) -> String {
|
||||
let name = sanitize_single_line(name);
|
||||
|
||||
match name.as_bytes() {
|
||||
[b'\'', .., b'\''] | [b'\"', .., b'\"'] | [b'<', .., b'>'] => name
|
||||
.get(1..name.len() - 1)
|
||||
.map_or("".to_string(), |s| s.trim().to_string()),
|
||||
_ => name.to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Sanitizes user input
|
||||
///
|
||||
/// - Removes newlines and trims the string
|
||||
/// - Removes potentially-malicious bidi characters
|
||||
pub fn sanitize_single_line(input: &str) -> String {
|
||||
sanitize_bidi_characters(input.replace(['\n', '\r'], " ").trim())
|
||||
}
|
||||
|
||||
const RTLO_CHARACTERS: [char; 5] = ['\u{202A}', '\u{202B}', '\u{202C}', '\u{202D}', '\u{202E}'];
|
||||
const ISOLATE_CHARACTERS: [char; 3] = ['\u{2066}', '\u{2067}', '\u{2068}'];
|
||||
const POP_ISOLATE_CHARACTER: char = '\u{2069}';
|
||||
/// Some control unicode characters can influence whether adjacent text is shown from
|
||||
/// left to right or from right to left.
|
||||
///
|
||||
/// Since user input is not supposed to influence how adjacent text looks,
|
||||
/// this function removes some of these characters.
|
||||
///
|
||||
/// Also see https://github.com/deltachat/deltachat-core-rust/issues/3479.
|
||||
pub fn sanitize_bidi_characters(input_str: &str) -> String {
|
||||
// RTLO_CHARACTERS are apparently rarely used in practice.
|
||||
// They can impact all following text, so, better remove them all:
|
||||
let input_str = input_str.replace(|char| RTLO_CHARACTERS.contains(&char), "");
|
||||
|
||||
// If the ISOLATE characters are not ended with a POP DIRECTIONAL ISOLATE character,
|
||||
// we regard the input as potentially malicious and simply remove all ISOLATE characters.
|
||||
// See https://en.wikipedia.org/wiki/Bidirectional_text#Unicode_bidi_support
|
||||
// and https://www.w3.org/International/questions/qa-bidi-unicode-controls.en
|
||||
// for an explanation about ISOLATE characters.
|
||||
fn isolate_characters_are_valid(input_str: &str) -> bool {
|
||||
let mut isolate_character_nesting: i32 = 0;
|
||||
for char in input_str.chars() {
|
||||
if ISOLATE_CHARACTERS.contains(&char) {
|
||||
isolate_character_nesting += 1;
|
||||
} else if char == POP_ISOLATE_CHARACTER {
|
||||
isolate_character_nesting -= 1;
|
||||
}
|
||||
|
||||
// According to Wikipedia, 125 levels are allowed:
|
||||
// https://en.wikipedia.org/wiki/Unicode_control_characters
|
||||
// (although, in practice, we could also significantly lower this number)
|
||||
if isolate_character_nesting < 0 || isolate_character_nesting > 125 {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
isolate_character_nesting == 0
|
||||
}
|
||||
|
||||
if isolate_characters_are_valid(&input_str) {
|
||||
input_str
|
||||
} else {
|
||||
input_str.replace(
|
||||
|char| ISOLATE_CHARACTERS.contains(&char) || POP_ISOLATE_CHARACTER == char,
|
||||
"",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns false if addr is an invalid address, otherwise true.
|
||||
pub fn may_be_valid_addr(addr: &str) -> bool {
|
||||
let res = EmailAddress::new(addr);
|
||||
res.is_ok()
|
||||
}
|
||||
|
||||
/// Returns address lowercased,
|
||||
/// with whitespace trimmed and `mailto:` prefix removed.
|
||||
pub fn addr_normalize(addr: &str) -> String {
|
||||
let norm = addr.trim().to_lowercase();
|
||||
|
||||
if norm.starts_with("mailto:") {
|
||||
norm.get(7..).unwrap_or(&norm).to_string()
|
||||
} else {
|
||||
norm
|
||||
}
|
||||
}
|
||||
|
||||
/// Compares two email addresses, normalizing them beforehand.
|
||||
pub fn addr_cmp(addr1: &str, addr2: &str) -> bool {
|
||||
let norm1 = addr_normalize(addr1);
|
||||
let norm2 = addr_normalize(addr2);
|
||||
|
||||
norm1 == norm2
|
||||
}
|
||||
|
||||
///
|
||||
/// Represents an email address, right now just the `name@domain` portion.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use deltachat_contact_tools::EmailAddress;
|
||||
/// let email = match EmailAddress::new("someone@example.com") {
|
||||
/// Ok(addr) => addr,
|
||||
/// Err(e) => panic!("Error parsing address, error was {}", e),
|
||||
/// };
|
||||
/// assert_eq!(&email.local, "someone");
|
||||
/// assert_eq!(&email.domain, "example.com");
|
||||
/// assert_eq!(email.to_string(), "someone@example.com");
|
||||
/// ```
|
||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||
pub struct EmailAddress {
|
||||
/// Local part of the email address.
|
||||
pub local: String,
|
||||
|
||||
/// Email address domain.
|
||||
pub domain: String,
|
||||
}
|
||||
|
||||
impl fmt::Display for EmailAddress {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{}@{}", self.local, self.domain)
|
||||
}
|
||||
}
|
||||
|
||||
impl EmailAddress {
|
||||
/// Performs a dead-simple parse of an email address.
|
||||
pub fn new(input: &str) -> Result<EmailAddress> {
|
||||
if input.is_empty() {
|
||||
bail!("empty string is not valid");
|
||||
}
|
||||
let parts: Vec<&str> = input.rsplitn(2, '@').collect();
|
||||
|
||||
if input
|
||||
.chars()
|
||||
.any(|c| c.is_whitespace() || c == '<' || c == '>')
|
||||
{
|
||||
bail!("Email {input:?} must not contain whitespaces, '>' or '<'");
|
||||
}
|
||||
|
||||
match &parts[..] {
|
||||
[domain, local] => {
|
||||
if local.is_empty() {
|
||||
bail!("empty string is not valid for local part in {input:?}");
|
||||
}
|
||||
if domain.is_empty() {
|
||||
bail!("missing domain after '@' in {input:?}");
|
||||
}
|
||||
if domain.ends_with('.') {
|
||||
bail!("Domain {domain:?} should not contain the dot in the end");
|
||||
}
|
||||
Ok(EmailAddress {
|
||||
local: (*local).to_string(),
|
||||
domain: (*domain).to_string(),
|
||||
})
|
||||
}
|
||||
_ => bail!("Email {input:?} must contain '@' character"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl rusqlite::types::ToSql for EmailAddress {
|
||||
fn to_sql(&self) -> rusqlite::Result<rusqlite::types::ToSqlOutput<'_>> {
|
||||
let val = rusqlite::types::Value::Text(self.to_string());
|
||||
let out = rusqlite::types::ToSqlOutput::Owned(val);
|
||||
Ok(out)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_contact_address() -> Result<()> {
|
||||
let alice_addr = "alice@example.org";
|
||||
let contact_address = ContactAddress::new(alice_addr)?;
|
||||
assert_eq!(contact_address.as_ref(), alice_addr);
|
||||
|
||||
let invalid_addr = "<> foobar";
|
||||
assert!(ContactAddress::new(invalid_addr).is_err());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_emailaddress_parse() {
|
||||
assert_eq!(EmailAddress::new("").is_ok(), false);
|
||||
assert_eq!(
|
||||
EmailAddress::new("user@domain.tld").unwrap(),
|
||||
EmailAddress {
|
||||
local: "user".into(),
|
||||
domain: "domain.tld".into(),
|
||||
}
|
||||
);
|
||||
assert_eq!(
|
||||
EmailAddress::new("user@localhost").unwrap(),
|
||||
EmailAddress {
|
||||
local: "user".into(),
|
||||
domain: "localhost".into()
|
||||
}
|
||||
);
|
||||
assert_eq!(EmailAddress::new("uuu").is_ok(), false);
|
||||
assert_eq!(EmailAddress::new("dd.tt").is_ok(), false);
|
||||
assert!(EmailAddress::new("tt.dd@uu").is_ok());
|
||||
assert!(EmailAddress::new("u@d").is_ok());
|
||||
assert!(EmailAddress::new("u@d.").is_err());
|
||||
assert!(EmailAddress::new("u@d.t").is_ok());
|
||||
assert_eq!(
|
||||
EmailAddress::new("u@d.tt").unwrap(),
|
||||
EmailAddress {
|
||||
local: "u".into(),
|
||||
domain: "d.tt".into(),
|
||||
}
|
||||
);
|
||||
assert!(EmailAddress::new("u@tt").is_ok());
|
||||
assert_eq!(EmailAddress::new("@d.tt").is_ok(), false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sanitize_name() {
|
||||
assert_eq!(&sanitize_name(" hello world "), "hello world");
|
||||
assert_eq!(&sanitize_name("<"), "<");
|
||||
assert_eq!(&sanitize_name(">"), ">");
|
||||
assert_eq!(&sanitize_name("'"), "'");
|
||||
assert_eq!(&sanitize_name("\""), "\"");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sanitize_single_line() {
|
||||
assert_eq!(sanitize_single_line("Hi\naiae "), "Hi aiae");
|
||||
assert_eq!(sanitize_single_line("\r\nahte\n\r"), "ahte");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sanitize_bidi_characters() {
|
||||
// Legit inputs:
|
||||
assert_eq!(
|
||||
&sanitize_bidi_characters("Tes\u{2067}ting Delta Chat\u{2069}"),
|
||||
"Tes\u{2067}ting Delta Chat\u{2069}"
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
&sanitize_bidi_characters("Tes\u{2067}ting \u{2068} Delta Chat\u{2069}\u{2069}"),
|
||||
"Tes\u{2067}ting \u{2068} Delta Chat\u{2069}\u{2069}"
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
&sanitize_bidi_characters("Tes\u{2067}ting\u{2069} Delta Chat\u{2067}\u{2069}"),
|
||||
"Tes\u{2067}ting\u{2069} Delta Chat\u{2067}\u{2069}"
|
||||
);
|
||||
|
||||
// Potentially-malicious inputs:
|
||||
assert_eq!(
|
||||
&sanitize_bidi_characters("Tes\u{202C}ting Delta Chat"),
|
||||
"Testing Delta Chat"
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
&sanitize_bidi_characters("Testing Delta Chat\u{2069}"),
|
||||
"Testing Delta Chat"
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
&sanitize_bidi_characters("Tes\u{2067}ting Delta Chat"),
|
||||
"Testing Delta Chat"
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
&sanitize_bidi_characters("Tes\u{2069}ting Delta Chat\u{2067}"),
|
||||
"Testing Delta Chat"
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
&sanitize_bidi_characters("Tes\u{2068}ting Delta Chat"),
|
||||
"Testing Delta Chat"
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,282 +0,0 @@
|
||||
use std::sync::LazyLock;
|
||||
|
||||
use anyhow::Context as _;
|
||||
use anyhow::Result;
|
||||
use chrono::DateTime;
|
||||
use chrono::NaiveDateTime;
|
||||
use regex::Regex;
|
||||
|
||||
use crate::sanitize_name_and_addr;
|
||||
|
||||
#[derive(Debug)]
|
||||
/// A Contact, as represented in a VCard.
|
||||
pub struct VcardContact {
|
||||
/// The email address, vcard property `email`
|
||||
pub addr: String,
|
||||
/// This must be the name authorized by the contact itself, not a locally given name. Vcard
|
||||
/// property `fn`. Can be empty, one should use `display_name()` to obtain the display name.
|
||||
pub authname: String,
|
||||
/// The contact's public PGP key in Base64, vcard property `key`
|
||||
pub key: Option<String>,
|
||||
/// The contact's profile image (=avatar) in Base64, vcard property `photo`
|
||||
pub profile_image: Option<String>,
|
||||
/// The biography, stored in the vcard property `note`
|
||||
pub biography: Option<String>,
|
||||
/// The timestamp when the vcard was created / last updated, vcard property `rev`
|
||||
pub timestamp: Result<i64>,
|
||||
}
|
||||
|
||||
impl VcardContact {
|
||||
/// Returns the contact's display name.
|
||||
pub fn display_name(&self) -> &str {
|
||||
match self.authname.is_empty() {
|
||||
false => &self.authname,
|
||||
true => &self.addr,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn escape(s: &str) -> String {
|
||||
// https://www.rfc-editor.org/rfc/rfc6350.html#section-3.4
|
||||
s
|
||||
// backslash must be first!
|
||||
.replace(r"\", r"\\")
|
||||
.replace(',', r"\,")
|
||||
.replace(';', r"\;")
|
||||
.replace('\n', r"\n")
|
||||
}
|
||||
|
||||
fn unescape(s: &str) -> String {
|
||||
// https://www.rfc-editor.org/rfc/rfc6350.html#section-3.4
|
||||
let mut out = String::new();
|
||||
|
||||
let mut chars = s.chars();
|
||||
while let Some(c) = chars.next() {
|
||||
if c == '\\' {
|
||||
if let Some(next) = chars.next() {
|
||||
match next {
|
||||
'\\' | ',' | ';' => out.push(next),
|
||||
'n' | 'N' => out.push('\n'),
|
||||
_ => {
|
||||
// Invalid escape sequence (keep unchanged)
|
||||
out.push('\\');
|
||||
out.push(next);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Invalid escape sequence (keep unchanged)
|
||||
out.push('\\');
|
||||
}
|
||||
} else {
|
||||
out.push(c);
|
||||
}
|
||||
}
|
||||
|
||||
out
|
||||
}
|
||||
|
||||
/// Returns a vCard containing given contacts.
|
||||
///
|
||||
/// Calling [`parse_vcard()`] on the returned result is a reverse operation.
|
||||
pub fn make_vcard(contacts: &[VcardContact]) -> String {
|
||||
fn format_timestamp(c: &VcardContact) -> Option<String> {
|
||||
let timestamp = *c.timestamp.as_ref().ok()?;
|
||||
let datetime = DateTime::from_timestamp(timestamp, 0)?;
|
||||
Some(datetime.format("%Y%m%dT%H%M%SZ").to_string())
|
||||
}
|
||||
|
||||
let mut res = "".to_string();
|
||||
for c in contacts {
|
||||
// Mustn't contain ',', but it's easier to escape than to error out.
|
||||
let addr = escape(&c.addr);
|
||||
let display_name = escape(c.display_name());
|
||||
res += &format!(
|
||||
"BEGIN:VCARD\r\n\
|
||||
VERSION:4.0\r\n\
|
||||
EMAIL:{addr}\r\n\
|
||||
FN:{display_name}\r\n"
|
||||
);
|
||||
if let Some(key) = &c.key {
|
||||
res += &format!("KEY:data:application/pgp-keys;base64\\,{key}\r\n");
|
||||
}
|
||||
if let Some(profile_image) = &c.profile_image {
|
||||
res += &format!("PHOTO:data:image/jpeg;base64\\,{profile_image}\r\n");
|
||||
}
|
||||
if let Some(biography) = &c.biography {
|
||||
res += &format!("NOTE:{}\r\n", escape(biography));
|
||||
}
|
||||
if let Some(timestamp) = format_timestamp(c) {
|
||||
res += &format!("REV:{timestamp}\r\n");
|
||||
}
|
||||
res += "END:VCARD\r\n";
|
||||
}
|
||||
res
|
||||
}
|
||||
|
||||
/// Parses `VcardContact`s from a given `&str`.
|
||||
pub fn parse_vcard(vcard: &str) -> Vec<VcardContact> {
|
||||
fn remove_prefix<'a>(s: &'a str, prefix: &str) -> Option<&'a str> {
|
||||
let start_of_s = s.get(..prefix.len())?;
|
||||
|
||||
if start_of_s.eq_ignore_ascii_case(prefix) {
|
||||
s.get(prefix.len()..)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
/// Returns (parameters, raw value) tuple.
|
||||
fn vcard_property_raw<'a>(line: &'a str, property: &str) -> Option<(&'a str, &'a str)> {
|
||||
let remainder = remove_prefix(line, property)?;
|
||||
// If `s` is `EMAIL;TYPE=work:alice@example.com` and `property` is `EMAIL`,
|
||||
// then `remainder` is now `;TYPE=work:alice@example.com`
|
||||
|
||||
// Note: This doesn't handle the case where there are quotes around a colon,
|
||||
// like `NAME;Foo="Some quoted text: that contains a colon":value`.
|
||||
// This could be improved in the future, but for now, the parsing is good enough.
|
||||
let (mut params, value) = remainder.split_once(':')?;
|
||||
// In the example from above, `params` is now `;TYPE=work`
|
||||
// and `value` is now `alice@example.com`
|
||||
|
||||
if params
|
||||
.chars()
|
||||
.next()
|
||||
.filter(|c| !c.is_ascii_punctuation() || *c == '_')
|
||||
.is_some()
|
||||
{
|
||||
// `s` started with `property`, but the next character after it was not punctuation,
|
||||
// so this line's property is actually something else
|
||||
return None;
|
||||
}
|
||||
if let Some(p) = remove_prefix(params, ";") {
|
||||
params = p;
|
||||
}
|
||||
if let Some(p) = remove_prefix(params, "PREF=1") {
|
||||
params = p;
|
||||
}
|
||||
Some((params, value))
|
||||
}
|
||||
/// Returns (parameters, unescaped value) tuple.
|
||||
fn vcard_property<'a>(line: &'a str, property: &str) -> Option<(&'a str, String)> {
|
||||
let (params, value) = vcard_property_raw(line, property)?;
|
||||
// Some fields can't contain commas, but unescape them everywhere for safety.
|
||||
Some((params, unescape(value)))
|
||||
}
|
||||
fn base64_key(line: &str) -> Option<&str> {
|
||||
let (params, value) = vcard_property_raw(line, "key")?;
|
||||
if params.eq_ignore_ascii_case("PGP;ENCODING=BASE64")
|
||||
|| params.eq_ignore_ascii_case("TYPE=PGP;ENCODING=b")
|
||||
{
|
||||
return Some(value);
|
||||
}
|
||||
remove_prefix(value, "data:application/pgp-keys;base64\\,")
|
||||
// Old Delta Chat format.
|
||||
.or_else(|| remove_prefix(value, "data:application/pgp-keys;base64,"))
|
||||
}
|
||||
fn base64_photo(line: &str) -> Option<&str> {
|
||||
let (params, value) = vcard_property_raw(line, "photo")?;
|
||||
if params.eq_ignore_ascii_case("JPEG;ENCODING=BASE64")
|
||||
|| params.eq_ignore_ascii_case("ENCODING=BASE64;JPEG")
|
||||
|| params.eq_ignore_ascii_case("TYPE=JPEG;ENCODING=b")
|
||||
|| params.eq_ignore_ascii_case("ENCODING=b;TYPE=JPEG")
|
||||
|| params.eq_ignore_ascii_case("ENCODING=BASE64;TYPE=JPEG")
|
||||
|| params.eq_ignore_ascii_case("TYPE=JPEG;ENCODING=BASE64")
|
||||
{
|
||||
return Some(value);
|
||||
}
|
||||
remove_prefix(value, "data:image/jpeg;base64\\,")
|
||||
// Old Delta Chat format.
|
||||
.or_else(|| remove_prefix(value, "data:image/jpeg;base64,"))
|
||||
}
|
||||
fn parse_datetime(datetime: &str) -> Result<i64> {
|
||||
// According to https://www.rfc-editor.org/rfc/rfc6350#section-4.3.5, the timestamp
|
||||
// is in ISO.8601.2004 format. DateTime::parse_from_rfc3339() apparently parses
|
||||
// ISO.8601, but fails to parse any of the examples given.
|
||||
// So, instead just parse using a format string.
|
||||
|
||||
// Parses 19961022T140000Z, 19961022T140000-05, or 19961022T140000-0500.
|
||||
let timestamp = match DateTime::parse_from_str(datetime, "%Y%m%dT%H%M%S%#z") {
|
||||
Ok(datetime) => datetime.timestamp(),
|
||||
// Parses 19961022T140000.
|
||||
Err(e) => match NaiveDateTime::parse_from_str(datetime, "%Y%m%dT%H%M%S") {
|
||||
Ok(datetime) => datetime
|
||||
.and_local_timezone(chrono::offset::Local)
|
||||
.single()
|
||||
.context("Could not apply local timezone to parsed date and time")?
|
||||
.timestamp(),
|
||||
Err(_) => return Err(e.into()),
|
||||
},
|
||||
};
|
||||
Ok(timestamp)
|
||||
}
|
||||
|
||||
// Remove line folding, see https://datatracker.ietf.org/doc/html/rfc6350#section-3.2
|
||||
static NEWLINE_AND_SPACE_OR_TAB: LazyLock<Regex> =
|
||||
LazyLock::new(|| Regex::new("\r?\n[\t ]").unwrap());
|
||||
let unfolded_lines = NEWLINE_AND_SPACE_OR_TAB.replace_all(vcard, "");
|
||||
|
||||
let mut lines = unfolded_lines.lines().peekable();
|
||||
let mut contacts = Vec::new();
|
||||
|
||||
while lines.peek().is_some() {
|
||||
// Skip to the start of the vcard:
|
||||
for line in lines.by_ref() {
|
||||
if line.eq_ignore_ascii_case("BEGIN:VCARD") {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let mut display_name = None;
|
||||
let mut addr = None;
|
||||
let mut key = None;
|
||||
let mut photo = None;
|
||||
let mut biography = None;
|
||||
let mut datetime = None;
|
||||
|
||||
for mut line in lines.by_ref() {
|
||||
if let Some(remainder) = remove_prefix(line, "item1.") {
|
||||
// Remove the group name, if the group is called "item1".
|
||||
// If necessary, we can improve this to also remove groups that are called something different that "item1".
|
||||
//
|
||||
// Search "group name" at https://datatracker.ietf.org/doc/html/rfc6350 for more infos.
|
||||
line = remainder;
|
||||
}
|
||||
|
||||
if let Some((_params, email)) = vcard_property(line, "email") {
|
||||
addr.get_or_insert(email);
|
||||
} else if let Some((_params, name)) = vcard_property(line, "fn") {
|
||||
display_name.get_or_insert(name);
|
||||
} else if let Some(k) = base64_key(line) {
|
||||
key.get_or_insert(k);
|
||||
} else if let Some(p) = base64_photo(line) {
|
||||
photo.get_or_insert(p);
|
||||
} else if let Some((_params, bio)) = vcard_property(line, "note") {
|
||||
biography.get_or_insert(bio);
|
||||
} else if let Some((_params, rev)) = vcard_property(line, "rev") {
|
||||
datetime.get_or_insert(rev);
|
||||
} else if line.eq_ignore_ascii_case("END:VCARD") {
|
||||
let (authname, addr) = sanitize_name_and_addr(
|
||||
&display_name.unwrap_or_default(),
|
||||
&addr.unwrap_or_default(),
|
||||
);
|
||||
|
||||
contacts.push(VcardContact {
|
||||
authname,
|
||||
addr,
|
||||
key: key.map(|s| s.to_string()),
|
||||
profile_image: photo.map(|s| s.to_string()),
|
||||
biography,
|
||||
timestamp: datetime
|
||||
.as_deref()
|
||||
.context("No timestamp in vcard")
|
||||
.and_then(parse_datetime),
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
contacts
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod vcard_tests;
|
||||
@@ -1,289 +0,0 @@
|
||||
use chrono::TimeZone as _;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_vcard_thunderbird() {
|
||||
let contacts = parse_vcard(
|
||||
"BEGIN:VCARD
|
||||
VERSION:4.0
|
||||
FN:'Alice Mueller'
|
||||
EMAIL;PREF=1:alice.mueller@posteo.de
|
||||
UID:a8083264-ca47-4be7-98a8-8ec3db1447ca
|
||||
END:VCARD
|
||||
BEGIN:VCARD
|
||||
VERSION:4.0
|
||||
FN:'bobzzz@freenet.de'
|
||||
EMAIL;PREF=1:bobzzz@freenet.de
|
||||
UID:cac4fef4-6351-4854-bbe4-9b6df857eaed
|
||||
END:VCARD
|
||||
",
|
||||
);
|
||||
|
||||
assert_eq!(contacts[0].addr, "alice.mueller@posteo.de".to_string());
|
||||
assert_eq!(contacts[0].authname, "Alice Mueller".to_string());
|
||||
assert_eq!(contacts[0].key, None);
|
||||
assert_eq!(contacts[0].profile_image, None);
|
||||
assert!(contacts[0].timestamp.is_err());
|
||||
|
||||
assert_eq!(contacts[1].addr, "bobzzz@freenet.de".to_string());
|
||||
assert_eq!(contacts[1].authname, "".to_string());
|
||||
assert_eq!(contacts[1].key, None);
|
||||
assert_eq!(contacts[1].profile_image, None);
|
||||
assert!(contacts[1].timestamp.is_err());
|
||||
|
||||
assert_eq!(contacts.len(), 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_vcard_simple_example() {
|
||||
let contacts = parse_vcard(
|
||||
"BEGIN:VCARD
|
||||
VERSION:4.0
|
||||
FN:Alice Wonderland
|
||||
N:Wonderland;Alice;;;Ms.
|
||||
GENDER:W
|
||||
EMAIL;TYPE=work:alice@example.com
|
||||
KEY;TYPE=PGP;ENCODING=b:[base64-data]
|
||||
REV:20240418T184242Z
|
||||
|
||||
END:VCARD",
|
||||
);
|
||||
|
||||
assert_eq!(contacts[0].addr, "alice@example.com".to_string());
|
||||
assert_eq!(contacts[0].authname, "Alice Wonderland".to_string());
|
||||
assert_eq!(contacts[0].key, Some("[base64-data]".to_string()));
|
||||
assert_eq!(contacts[0].profile_image, None);
|
||||
assert_eq!(*contacts[0].timestamp.as_ref().unwrap(), 1713465762);
|
||||
|
||||
assert_eq!(contacts.len(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_vcard_with_trailing_newline() {
|
||||
let contacts = parse_vcard(
|
||||
"BEGIN:VCARD\r
|
||||
VERSION:4.0\r
|
||||
FN:Alice Wonderland\r
|
||||
N:Wonderland;Alice;;;Ms.\r
|
||||
GENDER:W\r
|
||||
EMAIL;TYPE=work:alice@example.com\r
|
||||
KEY;TYPE=PGP;ENCODING=b:[base64-data]\r
|
||||
REV:20240418T184242Z\r
|
||||
END:VCARD\r
|
||||
\r",
|
||||
);
|
||||
|
||||
assert_eq!(contacts[0].addr, "alice@example.com".to_string());
|
||||
assert_eq!(contacts[0].authname, "Alice Wonderland".to_string());
|
||||
assert_eq!(contacts[0].key, Some("[base64-data]".to_string()));
|
||||
assert_eq!(contacts[0].profile_image, None);
|
||||
assert_eq!(*contacts[0].timestamp.as_ref().unwrap(), 1713465762);
|
||||
|
||||
assert_eq!(contacts.len(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_make_and_parse_vcard() {
|
||||
let contacts = [
|
||||
VcardContact {
|
||||
addr: "alice@example.org".to_string(),
|
||||
authname: "Alice Wonderland".to_string(),
|
||||
key: Some("[base64-data]".to_string()),
|
||||
profile_image: Some("image in Base64".to_string()),
|
||||
biography: Some("Hi,\nI'm Alice; and this is a backslash: \\".to_string()),
|
||||
timestamp: Ok(1713465762),
|
||||
},
|
||||
VcardContact {
|
||||
addr: "bob@example.com".to_string(),
|
||||
authname: "".to_string(),
|
||||
key: None,
|
||||
profile_image: None,
|
||||
biography: None,
|
||||
timestamp: Ok(0),
|
||||
},
|
||||
];
|
||||
let items = [
|
||||
"BEGIN:VCARD\r\n\
|
||||
VERSION:4.0\r\n\
|
||||
EMAIL:alice@example.org\r\n\
|
||||
FN:Alice Wonderland\r\n\
|
||||
KEY:data:application/pgp-keys;base64\\,[base64-data]\r\n\
|
||||
PHOTO:data:image/jpeg;base64\\,image in Base64\r\n\
|
||||
NOTE:Hi\\,\\nI'm Alice\\; and this is a backslash: \\\\\r\n\
|
||||
REV:20240418T184242Z\r\n\
|
||||
END:VCARD\r\n",
|
||||
"BEGIN:VCARD\r\n\
|
||||
VERSION:4.0\r\n\
|
||||
EMAIL:bob@example.com\r\n\
|
||||
FN:bob@example.com\r\n\
|
||||
REV:19700101T000000Z\r\n\
|
||||
END:VCARD\r\n",
|
||||
];
|
||||
let mut expected = "".to_string();
|
||||
for len in 0..=contacts.len() {
|
||||
let contacts = &contacts[0..len];
|
||||
let vcard = make_vcard(contacts);
|
||||
if len > 0 {
|
||||
expected += items[len - 1];
|
||||
}
|
||||
assert_eq!(vcard, expected);
|
||||
let parsed = parse_vcard(&vcard);
|
||||
assert_eq!(parsed.len(), contacts.len());
|
||||
for i in 0..parsed.len() {
|
||||
assert_eq!(parsed[i].addr, contacts[i].addr);
|
||||
assert_eq!(parsed[i].authname, contacts[i].authname);
|
||||
assert_eq!(parsed[i].key, contacts[i].key);
|
||||
assert_eq!(parsed[i].profile_image, contacts[i].profile_image);
|
||||
assert_eq!(
|
||||
parsed[i].timestamp.as_ref().unwrap(),
|
||||
contacts[i].timestamp.as_ref().unwrap()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_vcard_android() {
|
||||
let contacts = parse_vcard(
|
||||
"BEGIN:VCARD
|
||||
VERSION:2.1
|
||||
N:;Bob;;;
|
||||
FN:Bob
|
||||
TEL;CELL:+1-234-567-890
|
||||
EMAIL;HOME:bob@example.org
|
||||
END:VCARD
|
||||
BEGIN:VCARD
|
||||
VERSION:2.1
|
||||
N:;Alice;;;
|
||||
FN:Alice
|
||||
EMAIL;HOME:alice@example.org
|
||||
END:VCARD
|
||||
",
|
||||
);
|
||||
|
||||
assert_eq!(contacts[0].addr, "bob@example.org".to_string());
|
||||
assert_eq!(contacts[0].authname, "Bob".to_string());
|
||||
assert_eq!(contacts[0].key, None);
|
||||
assert_eq!(contacts[0].profile_image, None);
|
||||
|
||||
assert_eq!(contacts[1].addr, "alice@example.org".to_string());
|
||||
assert_eq!(contacts[1].authname, "Alice".to_string());
|
||||
assert_eq!(contacts[1].key, None);
|
||||
assert_eq!(contacts[1].profile_image, None);
|
||||
|
||||
assert_eq!(contacts.len(), 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_vcard_local_datetime() {
|
||||
let contacts = parse_vcard(
|
||||
"BEGIN:VCARD\n\
|
||||
VERSION:4.0\n\
|
||||
FN:Alice Wonderland\n\
|
||||
EMAIL;TYPE=work:alice@example.org\n\
|
||||
REV:20240418T184242\n\
|
||||
END:VCARD",
|
||||
);
|
||||
assert_eq!(contacts.len(), 1);
|
||||
assert_eq!(contacts[0].addr, "alice@example.org".to_string());
|
||||
assert_eq!(contacts[0].authname, "Alice Wonderland".to_string());
|
||||
assert_eq!(
|
||||
*contacts[0].timestamp.as_ref().unwrap(),
|
||||
chrono::offset::Local
|
||||
.with_ymd_and_hms(2024, 4, 18, 18, 42, 42)
|
||||
.unwrap()
|
||||
.timestamp()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_vcard_with_base64_avatar() {
|
||||
// This is not an actual base64-encoded avatar, it's just to test the parsing.
|
||||
// This one is Android-like.
|
||||
let vcard0 = "BEGIN:VCARD
|
||||
VERSION:2.1
|
||||
N:;Bob;;;
|
||||
FN:Bob
|
||||
EMAIL;HOME:bob@example.org
|
||||
PHOTO;ENCODING=BASE64;JPEG:/9j/4AAQSkZJRgABAQAAAQABAAD/4gIoSUNDX1BST0ZJTEU
|
||||
AAQEAAAIYAAAAAAQwAABtbnRyUkdCIFhZWiAAAAAAAAAAAAAAAABhY3NwAAAAAAAAAAAAAAAA
|
||||
L8bRuAJYoZUYrI4ZY3VWwxw4Ay28AAGBISScmf/2Q==
|
||||
|
||||
END:VCARD
|
||||
";
|
||||
// This one is DOS-like.
|
||||
let vcard1 = vcard0.replace('\n', "\r\n");
|
||||
for vcard in [vcard0, vcard1.as_str()] {
|
||||
let contacts = parse_vcard(vcard);
|
||||
assert_eq!(contacts.len(), 1);
|
||||
assert_eq!(contacts[0].addr, "bob@example.org".to_string());
|
||||
assert_eq!(contacts[0].authname, "Bob".to_string());
|
||||
assert_eq!(contacts[0].key, None);
|
||||
assert_eq!(contacts[0].profile_image.as_deref().unwrap(), "/9j/4AAQSkZJRgABAQAAAQABAAD/4gIoSUNDX1BST0ZJTEUAAQEAAAIYAAAAAAQwAABtbnRyUkdCIFhZWiAAAAAAAAAAAAAAAABhY3NwAAAAAAAAAAAAAAAAL8bRuAJYoZUYrI4ZY3VWwxw4Ay28AAGBISScmf/2Q==");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_protonmail_vcard() {
|
||||
let contacts = parse_vcard(
|
||||
"BEGIN:VCARD
|
||||
VERSION:4.0
|
||||
FN;PREF=1:Alice Wonderland
|
||||
UID:proton-web-03747582-328d-38dc-5ddd-000000000000
|
||||
ITEM1.EMAIL;PREF=1:alice@example.org
|
||||
ITEM1.KEY;PREF=1:data:application/pgp-keys;base64,aaaaaaaaaaaaaaaaaaaaaaaaa
|
||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
||||
ITEM1.KEY;PREF=2:data:application/pgp-keys;base64,bbbbbbbbbbbbbbbbbbbbbbbbb
|
||||
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
|
||||
ITEM1.X-PM-ENCRYPT:true
|
||||
ITEM1.X-PM-SIGN:true
|
||||
END:VCARD",
|
||||
);
|
||||
|
||||
assert_eq!(contacts.len(), 1);
|
||||
assert_eq!(&contacts[0].addr, "alice@example.org");
|
||||
assert_eq!(&contacts[0].authname, "Alice Wonderland");
|
||||
assert_eq!(contacts[0].key.as_ref().unwrap(), "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa");
|
||||
assert!(contacts[0].timestamp.is_err());
|
||||
assert_eq!(contacts[0].profile_image, None);
|
||||
}
|
||||
|
||||
/// Proton at some point slightly changed the format of their vcards.
|
||||
/// This also tests unescaped commas in PHOTO and KEY (old Delta Chat format).
|
||||
#[test]
|
||||
fn test_protonmail_vcard2() {
|
||||
let contacts = parse_vcard(
|
||||
r"BEGIN:VCARD
|
||||
VERSION:4.0
|
||||
FN;PREF=1:Alice
|
||||
PHOTO;PREF=1:data:image/jpeg;base64,/9aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/Z
|
||||
REV:Invalid Date
|
||||
ITEM1.EMAIL;PREF=1:alice@example.org
|
||||
KEY;PREF=1:data:application/pgp-keys;base64,xsaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa==
|
||||
UID:proton-web-aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa
|
||||
END:VCARD",
|
||||
);
|
||||
|
||||
assert_eq!(contacts.len(), 1);
|
||||
assert_eq!(&contacts[0].addr, "alice@example.org");
|
||||
assert_eq!(&contacts[0].authname, "Alice");
|
||||
assert_eq!(contacts[0].key.as_ref().unwrap(), "xsaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa==");
|
||||
assert!(contacts[0].timestamp.is_err());
|
||||
assert_eq!(contacts[0].profile_image.as_ref().unwrap(), "/9aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/Z");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_vcard_value_escape_unescape() {
|
||||
let original = "Text, with; chars and a \\ and a newline\nand a literal newline \\n";
|
||||
let expected_escaped = r"Text\, with\; chars and a \\ and a newline\nand a literal newline \\n";
|
||||
|
||||
let escaped = escape(original);
|
||||
assert_eq!(escaped, expected_escaped);
|
||||
let unescaped = unescape(&escaped);
|
||||
assert_eq!(original, unescaped);
|
||||
}
|
||||
@@ -1,10 +1,11 @@
|
||||
[package]
|
||||
name = "deltachat_ffi"
|
||||
version = "2.48.0-dev"
|
||||
version = "1.0.0-beta.8"
|
||||
description = "Deltachat FFI"
|
||||
authors = ["Delta Chat Developers (ML) <delta@codespeak.net>"]
|
||||
edition = "2018"
|
||||
readme = "README.md"
|
||||
license = "MPL-2.0"
|
||||
license = "MIT OR Apache-2.0"
|
||||
|
||||
keywords = ["deltachat", "chat", "openpgp", "email", "encryption"]
|
||||
categories = ["cryptography", "std", "email"]
|
||||
@@ -14,19 +15,15 @@ name = "deltachat"
|
||||
crate-type = ["cdylib", "staticlib"]
|
||||
|
||||
[dependencies]
|
||||
deltachat = { workspace = true, default-features = false }
|
||||
deltachat-jsonrpc = { workspace = true }
|
||||
libc = { workspace = true }
|
||||
human-panic = { version = "2", default-features = false }
|
||||
num-traits = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
tokio = { workspace = true, features = ["rt-multi-thread"] }
|
||||
anyhow = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
rand = { workspace = true }
|
||||
yerpc = { workspace = true, features = ["anyhow_expose"] }
|
||||
deltachat = { path = "../", default-features = false }
|
||||
deltachat-provider-database = "0.2.1"
|
||||
libc = "0.2"
|
||||
human-panic = "1.0.1"
|
||||
num-traits = "0.2.6"
|
||||
failure = "0.1.6"
|
||||
|
||||
[features]
|
||||
default = ["vendored"]
|
||||
vendored = ["deltachat/vendored", "deltachat-jsonrpc/vendored"]
|
||||
|
||||
default = ["vendored", "nightly", "ringbuf"]
|
||||
vendored = ["deltachat/vendored"]
|
||||
nightly = ["deltachat/nightly"]
|
||||
ringbuf = ["deltachat/ringbuf"]
|
||||
|
||||
@@ -1,29 +1,7 @@
|
||||
|
||||
:root {
|
||||
--accent: hsl(0 0% 85%);
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
--accent: hsl(0 0% 25%);
|
||||
}
|
||||
}
|
||||
|
||||
/* the code snippet frame, defaults to white which tends to get badly readable in combination with explaining text around */
|
||||
div.fragment {
|
||||
background-color: var(--accent);
|
||||
background-color: #e0e0e0;
|
||||
border: 0;
|
||||
padding: 1em;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
code {
|
||||
background-color: var(--accent);
|
||||
padding-left: .5em;
|
||||
padding-right: .5em;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
li {
|
||||
margin-bottom: .5em;
|
||||
}
|
||||
@@ -1,269 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<doxygenlayout version="2.0">
|
||||
<!-- Generated by doxygen 1.13.2 -->
|
||||
<!-- Navigation index tabs for HTML output -->
|
||||
<navindex>
|
||||
<tab type="mainpage" visible="yes" title=""/>
|
||||
<tab type="classes" visible="yes" title="">
|
||||
<tab type="classlist" visible="no" title="" intro=""/>
|
||||
<tab type="classindex" visible="no" title=""/>
|
||||
<tab type="hierarchy" visible="no" title="" intro=""/>
|
||||
<tab type="classmembers" visible="no" title="" intro=""/>
|
||||
</tab>
|
||||
<tab type="topics" visible="yes" title="Constants" intro="Here is a list of constants:"/>
|
||||
<tab type="pages" visible="yes" title="" intro=""/>
|
||||
<tab type="modules" visible="yes" title="" intro="">
|
||||
<tab type="modulelist" visible="yes" title="" intro=""/>
|
||||
<tab type="modulemembers" visible="yes" title="" intro=""/>
|
||||
</tab>
|
||||
<tab type="namespaces" visible="yes" title="">
|
||||
<tab type="namespacelist" visible="yes" title="" intro=""/>
|
||||
<tab type="namespacemembers" visible="yes" title="" intro=""/>
|
||||
</tab>
|
||||
<tab type="concepts" visible="yes" title="">
|
||||
</tab>
|
||||
<tab type="interfaces" visible="yes" title="">
|
||||
<tab type="interfacelist" visible="yes" title="" intro=""/>
|
||||
<tab type="interfaceindex" visible="$ALPHABETICAL_INDEX" title=""/>
|
||||
<tab type="interfacehierarchy" visible="yes" title="" intro=""/>
|
||||
</tab>
|
||||
<tab type="structs" visible="yes" title="">
|
||||
<tab type="structlist" visible="yes" title="" intro=""/>
|
||||
<tab type="structindex" visible="$ALPHABETICAL_INDEX" title=""/>
|
||||
</tab>
|
||||
<tab type="exceptions" visible="yes" title="">
|
||||
<tab type="exceptionlist" visible="yes" title="" intro=""/>
|
||||
<tab type="exceptionindex" visible="$ALPHABETICAL_INDEX" title=""/>
|
||||
<tab type="exceptionhierarchy" visible="yes" title="" intro=""/>
|
||||
</tab>
|
||||
<tab type="files" visible="yes" title="">
|
||||
<tab type="filelist" visible="yes" title="" intro=""/>
|
||||
<tab type="globals" visible="yes" title="" intro=""/>
|
||||
</tab>
|
||||
<tab type="examples" visible="yes" title="" intro=""/>
|
||||
</navindex>
|
||||
|
||||
<!-- Layout definition for a class page -->
|
||||
<class>
|
||||
<briefdescription visible="yes"/>
|
||||
<includes visible="$SHOW_HEADERFILE"/>
|
||||
<inheritancegraph visible="yes"/>
|
||||
<collaborationgraph visible="yes"/>
|
||||
<memberdecl>
|
||||
<nestedclasses visible="yes" title=""/>
|
||||
<publictypes visible="yes" title=""/>
|
||||
<services visible="yes" title=""/>
|
||||
<interfaces visible="yes" title=""/>
|
||||
<publicslots visible="yes" title=""/>
|
||||
<signals visible="yes" title=""/>
|
||||
<publicmethods visible="yes" title=""/>
|
||||
<publicstaticmethods visible="yes" title=""/>
|
||||
<publicattributes visible="yes" title=""/>
|
||||
<publicstaticattributes visible="yes" title=""/>
|
||||
<protectedtypes visible="yes" title=""/>
|
||||
<protectedslots visible="yes" title=""/>
|
||||
<protectedmethods visible="yes" title=""/>
|
||||
<protectedstaticmethods visible="yes" title=""/>
|
||||
<protectedattributes visible="yes" title=""/>
|
||||
<protectedstaticattributes visible="yes" title=""/>
|
||||
<packagetypes visible="yes" title=""/>
|
||||
<packagemethods visible="yes" title=""/>
|
||||
<packagestaticmethods visible="yes" title=""/>
|
||||
<packageattributes visible="yes" title=""/>
|
||||
<packagestaticattributes visible="yes" title=""/>
|
||||
<properties visible="yes" title=""/>
|
||||
<events visible="yes" title=""/>
|
||||
<privatetypes visible="yes" title=""/>
|
||||
<privateslots visible="yes" title=""/>
|
||||
<privatemethods visible="yes" title=""/>
|
||||
<privatestaticmethods visible="yes" title=""/>
|
||||
<privateattributes visible="yes" title=""/>
|
||||
<privatestaticattributes visible="yes" title=""/>
|
||||
<friends visible="yes" title=""/>
|
||||
<related visible="yes" title="" subtitle=""/>
|
||||
<membergroups visible="yes"/>
|
||||
</memberdecl>
|
||||
<detaileddescription visible="yes" title=""/>
|
||||
<memberdef>
|
||||
<inlineclasses visible="yes" title=""/>
|
||||
<typedefs visible="yes" title=""/>
|
||||
<enums visible="yes" title=""/>
|
||||
<services visible="yes" title=""/>
|
||||
<interfaces visible="yes" title=""/>
|
||||
<constructors visible="yes" title=""/>
|
||||
<functions visible="yes" title=""/>
|
||||
<related visible="yes" title=""/>
|
||||
<variables visible="yes" title=""/>
|
||||
<properties visible="yes" title=""/>
|
||||
<events visible="yes" title=""/>
|
||||
</memberdef>
|
||||
<allmemberslink visible="yes"/>
|
||||
<usedfiles visible="$SHOW_USED_FILES"/>
|
||||
<authorsection visible="yes"/>
|
||||
</class>
|
||||
|
||||
<!-- Layout definition for a namespace page -->
|
||||
<namespace>
|
||||
<briefdescription visible="yes"/>
|
||||
<memberdecl>
|
||||
<nestednamespaces visible="yes" title=""/>
|
||||
<constantgroups visible="yes" title=""/>
|
||||
<interfaces visible="yes" title=""/>
|
||||
<classes visible="yes" title=""/>
|
||||
<concepts visible="yes" title=""/>
|
||||
<structs visible="yes" title=""/>
|
||||
<exceptions visible="yes" title=""/>
|
||||
<typedefs visible="yes" title=""/>
|
||||
<sequences visible="yes" title=""/>
|
||||
<dictionaries visible="yes" title=""/>
|
||||
<enums visible="yes" title=""/>
|
||||
<functions visible="yes" title=""/>
|
||||
<variables visible="yes" title=""/>
|
||||
<properties visible="yes" title=""/>
|
||||
<membergroups visible="yes" visible="yes"/>
|
||||
</memberdecl>
|
||||
<detaileddescription visible="yes" title=""/>
|
||||
<memberdef>
|
||||
<inlineclasses visible="yes" title=""/>
|
||||
<typedefs visible="yes" title=""/>
|
||||
<sequences visible="yes" title=""/>
|
||||
<dictionaries visible="yes" title=""/>
|
||||
<enums visible="yes" title=""/>
|
||||
<functions visible="yes" title=""/>
|
||||
<variables visible="yes" title=""/>
|
||||
<properties visible="yes" title=""/>
|
||||
</memberdef>
|
||||
<authorsection visible="yes"/>
|
||||
</namespace>
|
||||
|
||||
<!-- Layout definition for a concept page -->
|
||||
<concept>
|
||||
<briefdescription visible="yes"/>
|
||||
<includes visible="$SHOW_HEADERFILE"/>
|
||||
<definition visible="yes" title=""/>
|
||||
<detaileddescription visible="yes" title=""/>
|
||||
<authorsection visible="yes"/>
|
||||
</concept>
|
||||
|
||||
<!-- Layout definition for a file page -->
|
||||
<file>
|
||||
<briefdescription visible="yes"/>
|
||||
<includes visible="$SHOW_INCLUDE_FILES"/>
|
||||
<includegraph visible="yes"/>
|
||||
<includedbygraph visible="yes"/>
|
||||
<sourcelink visible="yes"/>
|
||||
<memberdecl>
|
||||
<interfaces visible="yes" title=""/>
|
||||
<classes visible="yes" title=""/>
|
||||
<structs visible="yes" title=""/>
|
||||
<exceptions visible="yes" title=""/>
|
||||
<namespaces visible="yes" title=""/>
|
||||
<concepts visible="yes" title=""/>
|
||||
<constantgroups visible="yes" title=""/>
|
||||
<defines visible="yes" title=""/>
|
||||
<typedefs visible="yes" title=""/>
|
||||
<sequences visible="yes" title=""/>
|
||||
<dictionaries visible="yes" title=""/>
|
||||
<enums visible="yes" title=""/>
|
||||
<functions visible="yes" title=""/>
|
||||
<variables visible="yes" title=""/>
|
||||
<properties visible="yes" title=""/>
|
||||
<membergroups visible="yes" visible="yes"/>
|
||||
</memberdecl>
|
||||
<detaileddescription visible="yes" title=""/>
|
||||
<memberdef>
|
||||
<inlineclasses visible="yes" title=""/>
|
||||
<defines visible="yes" title=""/>
|
||||
<typedefs visible="yes" title=""/>
|
||||
<sequences visible="yes" title=""/>
|
||||
<dictionaries visible="yes" title=""/>
|
||||
<enums visible="yes" title=""/>
|
||||
<functions visible="yes" title=""/>
|
||||
<variables visible="yes" title=""/>
|
||||
<properties visible="yes" title=""/>
|
||||
</memberdef>
|
||||
<authorsection/>
|
||||
</file>
|
||||
|
||||
<!-- Layout definition for a group page -->
|
||||
<group>
|
||||
<briefdescription visible="yes"/>
|
||||
<groupgraph visible="yes"/>
|
||||
<memberdecl>
|
||||
<nestedgroups visible="yes" title=""/>
|
||||
<modules visible="yes" title=""/>
|
||||
<dirs visible="yes" title=""/>
|
||||
<files visible="yes" title=""/>
|
||||
<namespaces visible="yes" title=""/>
|
||||
<concepts visible="yes" title=""/>
|
||||
<classes visible="yes" title=""/>
|
||||
<defines visible="yes" title=""/>
|
||||
<typedefs visible="yes" title=""/>
|
||||
<sequences visible="yes" title=""/>
|
||||
<dictionaries visible="yes" title=""/>
|
||||
<enums visible="yes" title=""/>
|
||||
<enumvalues visible="yes" title=""/>
|
||||
<functions visible="yes" title=""/>
|
||||
<variables visible="yes" title=""/>
|
||||
<signals visible="yes" title=""/>
|
||||
<publicslots visible="yes" title=""/>
|
||||
<protectedslots visible="yes" title=""/>
|
||||
<privateslots visible="yes" title=""/>
|
||||
<events visible="yes" title=""/>
|
||||
<properties visible="yes" title=""/>
|
||||
<friends visible="yes" title=""/>
|
||||
<membergroups visible="yes"/>
|
||||
</memberdecl>
|
||||
<detaileddescription visible="yes" title=""/>
|
||||
<memberdef>
|
||||
<pagedocs/>
|
||||
<inlineclasses visible="yes" title=""/>
|
||||
<defines visible="yes" title=""/>
|
||||
<typedefs visible="yes" title=""/>
|
||||
<sequences visible="yes" title=""/>
|
||||
<dictionaries visible="yes" title=""/>
|
||||
<enums visible="yes" title=""/>
|
||||
<enumvalues visible="yes" title=""/>
|
||||
<functions visible="yes" title=""/>
|
||||
<variables visible="yes" title=""/>
|
||||
<signals visible="yes" title=""/>
|
||||
<publicslots visible="yes" title=""/>
|
||||
<protectedslots visible="yes" title=""/>
|
||||
<privateslots visible="yes" title=""/>
|
||||
<events visible="yes" title=""/>
|
||||
<properties visible="yes" title=""/>
|
||||
<friends visible="yes" title=""/>
|
||||
</memberdef>
|
||||
<authorsection visible="yes"/>
|
||||
</group>
|
||||
|
||||
<!-- Layout definition for a C++20 module page -->
|
||||
<module>
|
||||
<briefdescription visible="yes"/>
|
||||
<exportedmodules visible="yes"/>
|
||||
<memberdecl>
|
||||
<concepts visible="yes" title=""/>
|
||||
<classes visible="yes" title=""/>
|
||||
<enums visible="yes" title=""/>
|
||||
<typedefs visible="yes" title=""/>
|
||||
<functions visible="yes" title=""/>
|
||||
<variables visible="yes" title=""/>
|
||||
<membergroups visible="yes" title=""/>
|
||||
</memberdecl>
|
||||
<detaileddescription visible="yes" title=""/>
|
||||
<memberdecl>
|
||||
<files visible="yes"/>
|
||||
</memberdecl>
|
||||
</module>
|
||||
|
||||
<!-- Layout definition for a directory page -->
|
||||
<directory>
|
||||
<briefdescription visible="yes"/>
|
||||
<directorygraph visible="yes"/>
|
||||
<memberdecl>
|
||||
<dirs visible="yes"/>
|
||||
<files visible="yes"/>
|
||||
</memberdecl>
|
||||
<detaileddescription visible="yes" title=""/>
|
||||
</directory>
|
||||
</doxygenlayout>
|
||||
@@ -1,9 +1,5 @@
|
||||
# Delta Chat C Interface
|
||||
|
||||
## Installation
|
||||
|
||||
see `Installing libdeltachat system wide` in [../README.md](../README.md)
|
||||
|
||||
## Documentation
|
||||
|
||||
To generate the C Interface documentation,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use std::io::Write;
|
||||
use std::path::PathBuf;
|
||||
use std::{env, fs};
|
||||
|
||||
@@ -18,18 +19,15 @@ fn main() {
|
||||
include_str!("deltachat.pc.in"),
|
||||
name = "deltachat",
|
||||
description = env::var("CARGO_PKG_DESCRIPTION").unwrap(),
|
||||
url = env::var("CARGO_PKG_HOMEPAGE").unwrap_or_else(|_| "".to_string()),
|
||||
url = env::var("CARGO_PKG_HOMEPAGE").unwrap_or("".to_string()),
|
||||
version = env::var("CARGO_PKG_VERSION").unwrap(),
|
||||
libs_priv = libs_priv,
|
||||
prefix = env::var("PREFIX").unwrap_or_else(|_| "/usr/local".to_string()),
|
||||
libdir = env::var("LIBDIR").unwrap_or_else(|_| "/usr/local/lib".to_string()),
|
||||
includedir = env::var("INCLUDEDIR").unwrap_or_else(|_| "/usr/local/include".to_string()),
|
||||
prefix = env::var("PREFIX").unwrap_or("/usr/local".to_string()),
|
||||
);
|
||||
|
||||
fs::create_dir_all(target_path.join("pkgconfig")).unwrap();
|
||||
fs::write(
|
||||
target_path.join("pkgconfig").join("deltachat.pc"),
|
||||
pkg_config.as_bytes(),
|
||||
)
|
||||
.unwrap();
|
||||
fs::File::create(target_path.join("pkgconfig").join("deltachat.pc"))
|
||||
.unwrap()
|
||||
.write_all(&pkg_config.as_bytes())
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
prefix={prefix}
|
||||
libdir={libdir}
|
||||
includedir={includedir}
|
||||
libdir=${{prefix}}/lib
|
||||
includedir=${{prefix}}/include
|
||||
|
||||
Name: {name}
|
||||
Description: {description}
|
||||
|
||||
@@ -1,139 +0,0 @@
|
||||
use crate::chat::ChatItem;
|
||||
use crate::constants::DC_MSG_ID_DAYMARKER;
|
||||
use crate::contact::ContactId;
|
||||
use crate::location::Location;
|
||||
use crate::message::MsgId;
|
||||
|
||||
/* * the structure behind dc_array_t */
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum dc_array_t {
|
||||
MsgIds(Vec<MsgId>),
|
||||
ContactIds(Vec<ContactId>),
|
||||
Chat(Vec<ChatItem>),
|
||||
Locations(Vec<Location>),
|
||||
Uint(Vec<u32>),
|
||||
}
|
||||
|
||||
impl dc_array_t {
|
||||
pub(crate) fn get_id(&self, index: usize) -> u32 {
|
||||
match self {
|
||||
Self::MsgIds(array) => array[index].to_u32(),
|
||||
Self::ContactIds(array) => array[index].to_u32(),
|
||||
Self::Chat(array) => match array[index] {
|
||||
ChatItem::Message { msg_id } => msg_id.to_u32(),
|
||||
ChatItem::DayMarker { .. } => DC_MSG_ID_DAYMARKER,
|
||||
},
|
||||
Self::Locations(array) => array[index].location_id,
|
||||
Self::Uint(array) => array[index],
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn get_timestamp(&self, index: usize) -> Option<i64> {
|
||||
match self {
|
||||
Self::MsgIds(_) => None,
|
||||
Self::ContactIds(_) => None,
|
||||
Self::Chat(array) => array.get(index).and_then(|item| match item {
|
||||
ChatItem::Message { .. } => None,
|
||||
ChatItem::DayMarker { timestamp } => Some(*timestamp),
|
||||
}),
|
||||
Self::Locations(array) => array.get(index).map(|location| location.timestamp),
|
||||
Self::Uint(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn get_marker(&self, index: usize) -> Option<&str> {
|
||||
match self {
|
||||
Self::MsgIds(_) => None,
|
||||
Self::ContactIds(_) => None,
|
||||
Self::Chat(_) => None,
|
||||
Self::Locations(array) => array
|
||||
.get(index)
|
||||
.and_then(|location| location.marker.as_deref()),
|
||||
Self::Uint(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn get_location(&self, index: usize) -> &Location {
|
||||
if let Self::Locations(array) = self {
|
||||
&array[index]
|
||||
} else {
|
||||
panic!("Not an array of locations")
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the number of elements in the array.
|
||||
pub(crate) fn len(&self) -> usize {
|
||||
match self {
|
||||
Self::MsgIds(array) => array.len(),
|
||||
Self::ContactIds(array) => array.len(),
|
||||
Self::Chat(array) => array.len(),
|
||||
Self::Locations(array) => array.len(),
|
||||
Self::Uint(array) => array.len(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn search_id(&self, needle: u32) -> Option<usize> {
|
||||
(0..self.len()).find(|i| self.get_id(*i) == needle)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Vec<u32>> for dc_array_t {
|
||||
fn from(array: Vec<u32>) -> Self {
|
||||
dc_array_t::Uint(array)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Vec<MsgId>> for dc_array_t {
|
||||
fn from(array: Vec<MsgId>) -> Self {
|
||||
dc_array_t::MsgIds(array)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Vec<ContactId>> for dc_array_t {
|
||||
fn from(array: Vec<ContactId>) -> Self {
|
||||
dc_array_t::ContactIds(array)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Vec<ChatItem>> for dc_array_t {
|
||||
fn from(array: Vec<ChatItem>) -> Self {
|
||||
dc_array_t::Chat(array)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Vec<Location>> for dc_array_t {
|
||||
fn from(array: Vec<Location>) -> Self {
|
||||
dc_array_t::Locations(array)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_dc_array() {
|
||||
let arr: dc_array_t = Vec::<u32>::new().into();
|
||||
assert!(arr.len() == 0);
|
||||
|
||||
let ids: Vec<u32> = (2..1002).collect();
|
||||
let arr: dc_array_t = ids.into();
|
||||
|
||||
assert_eq!(arr.len(), 1000);
|
||||
|
||||
for i in 0..1000 {
|
||||
assert_eq!(arr.get_id(i), (i + 2) as u32);
|
||||
}
|
||||
|
||||
assert_eq!(arr.search_id(10), Some(8));
|
||||
assert_eq!(arr.search_id(1), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn test_dc_array_out_of_bounds() {
|
||||
let ids: Vec<u32> = (2..1002).collect();
|
||||
let arr: dc_array_t = ids.into();
|
||||
arr.get_id(1000);
|
||||
}
|
||||
}
|
||||
@@ -1,276 +0,0 @@
|
||||
//! # Legacy generic return values for C API.
|
||||
|
||||
use std::borrow::Cow;
|
||||
|
||||
use anyhow::Error;
|
||||
|
||||
use crate::message::MessageState;
|
||||
use crate::qr::Qr;
|
||||
use crate::summary::{Summary, SummaryPrefix};
|
||||
|
||||
/// An object containing a set of values.
|
||||
/// The meaning of the values is defined by the function returning the object.
|
||||
/// Lot objects are created
|
||||
/// eg. by chatlist.get_summary() or dc_msg_get_summary().
|
||||
///
|
||||
/// *Lot* is used in the meaning *heap* here.
|
||||
// The QR code grew too large. So be it.
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
#[derive(Debug)]
|
||||
pub enum Lot {
|
||||
Summary(Summary),
|
||||
Qr(Qr),
|
||||
Error(String),
|
||||
}
|
||||
|
||||
#[repr(u8)]
|
||||
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum Meaning {
|
||||
#[default]
|
||||
None = 0,
|
||||
Text1Draft = 1,
|
||||
Text1Username = 2,
|
||||
Text1Self = 3,
|
||||
}
|
||||
|
||||
impl Lot {
|
||||
pub fn get_text1(&self) -> Option<Cow<'_, str>> {
|
||||
match self {
|
||||
Self::Summary(summary) => match &summary.prefix {
|
||||
None => None,
|
||||
Some(SummaryPrefix::Draft(text)) => Some(Cow::Borrowed(text)),
|
||||
Some(SummaryPrefix::Username(username)) => Some(Cow::Borrowed(username)),
|
||||
Some(SummaryPrefix::Me(text)) => Some(Cow::Borrowed(text)),
|
||||
},
|
||||
Self::Qr(qr) => match qr {
|
||||
Qr::AskVerifyContact { .. } => None,
|
||||
Qr::AskVerifyGroup { grpname, .. } => Some(Cow::Borrowed(grpname)),
|
||||
Qr::AskJoinBroadcast { name, .. } => Some(Cow::Borrowed(name)),
|
||||
Qr::FprOk { .. } => None,
|
||||
Qr::FprMismatch { .. } => None,
|
||||
Qr::FprWithoutAddr { fingerprint, .. } => Some(Cow::Borrowed(fingerprint)),
|
||||
Qr::Account { domain } => Some(Cow::Borrowed(domain)),
|
||||
Qr::Backup2 { .. } => None,
|
||||
Qr::BackupTooNew { .. } => None,
|
||||
Qr::Proxy { host, port, .. } => Some(Cow::Owned(format!("{host}:{port}"))),
|
||||
Qr::Addr { draft, .. } => draft.as_deref().map(Cow::Borrowed),
|
||||
Qr::Url { url } => Some(Cow::Borrowed(url)),
|
||||
Qr::Text { text } => Some(Cow::Borrowed(text)),
|
||||
Qr::WithdrawVerifyContact { .. } => None,
|
||||
Qr::WithdrawVerifyGroup { grpname, .. } => Some(Cow::Borrowed(grpname)),
|
||||
Qr::WithdrawJoinBroadcast { name, .. } => Some(Cow::Borrowed(name)),
|
||||
Qr::ReviveVerifyContact { .. } => None,
|
||||
Qr::ReviveVerifyGroup { grpname, .. } => Some(Cow::Borrowed(grpname)),
|
||||
Qr::ReviveJoinBroadcast { name, .. } => Some(Cow::Borrowed(name)),
|
||||
Qr::Login { address, .. } => Some(Cow::Borrowed(address)),
|
||||
},
|
||||
Self::Error(err) => Some(Cow::Borrowed(err)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_text2(&self) -> Option<Cow<'_, str>> {
|
||||
match self {
|
||||
Self::Summary(summary) => Some(summary.truncated_text(160)),
|
||||
Self::Qr(_) => None,
|
||||
Self::Error(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_text1_meaning(&self) -> Meaning {
|
||||
match self {
|
||||
Self::Summary(summary) => match &summary.prefix {
|
||||
None => Meaning::None,
|
||||
Some(SummaryPrefix::Draft(_text)) => Meaning::Text1Draft,
|
||||
Some(SummaryPrefix::Username(_username)) => Meaning::Text1Username,
|
||||
Some(SummaryPrefix::Me(_text)) => Meaning::Text1Self,
|
||||
},
|
||||
Self::Qr(qr) => match qr {
|
||||
Qr::Addr {
|
||||
draft: Some(_draft),
|
||||
..
|
||||
} => Meaning::Text1Draft,
|
||||
_ => Meaning::None,
|
||||
},
|
||||
Self::Error(_err) => Meaning::None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_state(&self) -> LotState {
|
||||
match self {
|
||||
Self::Summary(summary) => summary.state.into(),
|
||||
Self::Qr(qr) => match qr {
|
||||
Qr::AskVerifyContact { .. } => LotState::QrAskVerifyContact,
|
||||
Qr::AskVerifyGroup { .. } => LotState::QrAskVerifyGroup,
|
||||
Qr::AskJoinBroadcast { .. } => LotState::QrAskJoinBroadcast,
|
||||
Qr::FprOk { .. } => LotState::QrFprOk,
|
||||
Qr::FprMismatch { .. } => LotState::QrFprMismatch,
|
||||
Qr::FprWithoutAddr { .. } => LotState::QrFprWithoutAddr,
|
||||
Qr::Account { .. } => LotState::QrAccount,
|
||||
Qr::Backup2 { .. } => LotState::QrBackup2,
|
||||
Qr::BackupTooNew { .. } => LotState::QrBackupTooNew,
|
||||
Qr::Proxy { .. } => LotState::QrProxy,
|
||||
Qr::Addr { .. } => LotState::QrAddr,
|
||||
Qr::Url { .. } => LotState::QrUrl,
|
||||
Qr::Text { .. } => LotState::QrText,
|
||||
Qr::WithdrawVerifyContact { .. } => LotState::QrWithdrawVerifyContact,
|
||||
Qr::WithdrawVerifyGroup { .. } => LotState::QrWithdrawVerifyGroup,
|
||||
Qr::WithdrawJoinBroadcast { .. } => LotState::QrWithdrawJoinBroadcast,
|
||||
Qr::ReviveVerifyContact { .. } => LotState::QrReviveVerifyContact,
|
||||
Qr::ReviveVerifyGroup { .. } => LotState::QrReviveVerifyGroup,
|
||||
Qr::ReviveJoinBroadcast { .. } => LotState::QrReviveJoinBroadcast,
|
||||
Qr::Login { .. } => LotState::QrLogin,
|
||||
},
|
||||
Self::Error(_err) => LotState::QrError,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_id(&self) -> u32 {
|
||||
match self {
|
||||
Self::Summary(_) => Default::default(),
|
||||
Self::Qr(qr) => match qr {
|
||||
Qr::AskVerifyContact { contact_id, .. } => contact_id.to_u32(),
|
||||
Qr::AskVerifyGroup { .. } => Default::default(),
|
||||
Qr::AskJoinBroadcast { .. } => Default::default(),
|
||||
Qr::FprOk { contact_id } => contact_id.to_u32(),
|
||||
Qr::FprMismatch { contact_id } => contact_id.unwrap_or_default().to_u32(),
|
||||
Qr::FprWithoutAddr { .. } => Default::default(),
|
||||
Qr::Account { .. } => Default::default(),
|
||||
Qr::Backup2 { .. } => Default::default(),
|
||||
Qr::BackupTooNew { .. } => Default::default(),
|
||||
Qr::Proxy { .. } => Default::default(),
|
||||
Qr::Addr { contact_id, .. } => contact_id.to_u32(),
|
||||
Qr::Url { .. } => Default::default(),
|
||||
Qr::Text { .. } => Default::default(),
|
||||
Qr::WithdrawVerifyContact { contact_id, .. } => contact_id.to_u32(),
|
||||
Qr::WithdrawVerifyGroup { .. } | Qr::WithdrawJoinBroadcast { .. } => {
|
||||
Default::default()
|
||||
}
|
||||
Qr::ReviveVerifyContact { contact_id, .. } => contact_id.to_u32(),
|
||||
Qr::ReviveVerifyGroup { .. } | Qr::ReviveJoinBroadcast { .. } => Default::default(),
|
||||
Qr::Login { .. } => Default::default(),
|
||||
},
|
||||
Self::Error(_) => Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_timestamp(&self) -> i64 {
|
||||
match self {
|
||||
Self::Summary(summary) => summary.timestamp,
|
||||
Self::Qr(_) => Default::default(),
|
||||
Self::Error(_) => Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(u32)]
|
||||
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum LotState {
|
||||
#[default]
|
||||
Undefined = 0,
|
||||
|
||||
// Qr States
|
||||
/// id=contact
|
||||
QrAskVerifyContact = 200,
|
||||
|
||||
/// text1=groupname
|
||||
QrAskVerifyGroup = 202,
|
||||
|
||||
/// text1=broadcast_name
|
||||
QrAskJoinBroadcast = 204,
|
||||
|
||||
/// id=contact
|
||||
QrFprOk = 210,
|
||||
|
||||
/// id=contact
|
||||
QrFprMismatch = 220,
|
||||
|
||||
/// text1=formatted fingerprint
|
||||
QrFprWithoutAddr = 230,
|
||||
|
||||
/// text1=domain
|
||||
QrAccount = 250,
|
||||
|
||||
QrBackup2 = 252,
|
||||
|
||||
QrBackupTooNew = 255,
|
||||
|
||||
/// text1=address, text2=protocol
|
||||
QrProxy = 271,
|
||||
|
||||
/// id=contact
|
||||
QrAddr = 320,
|
||||
|
||||
/// text1=text
|
||||
QrText = 330,
|
||||
|
||||
/// text1=URL
|
||||
QrUrl = 332,
|
||||
|
||||
/// text1=error string
|
||||
QrError = 400,
|
||||
|
||||
QrWithdrawVerifyContact = 500,
|
||||
|
||||
/// text1=groupname
|
||||
QrWithdrawVerifyGroup = 502,
|
||||
/// text1=broadcast channel name
|
||||
QrWithdrawJoinBroadcast = 504,
|
||||
|
||||
QrReviveVerifyContact = 510,
|
||||
|
||||
/// text1=groupname
|
||||
QrReviveVerifyGroup = 512,
|
||||
/// text1=groupname
|
||||
QrReviveJoinBroadcast = 514,
|
||||
|
||||
/// text1=email_address
|
||||
QrLogin = 520,
|
||||
|
||||
// Message States
|
||||
MsgInFresh = 10,
|
||||
MsgInNoticed = 13,
|
||||
MsgInSeen = 16,
|
||||
MsgOutPreparing = 18,
|
||||
MsgOutDraft = 19,
|
||||
MsgOutPending = 20,
|
||||
MsgOutFailed = 24,
|
||||
MsgOutDelivered = 26,
|
||||
MsgOutMdnRcvd = 28,
|
||||
}
|
||||
|
||||
impl From<MessageState> for LotState {
|
||||
fn from(s: MessageState) -> Self {
|
||||
use MessageState::*;
|
||||
match s {
|
||||
Undefined => LotState::Undefined,
|
||||
InFresh => LotState::MsgInFresh,
|
||||
InNoticed => LotState::MsgInNoticed,
|
||||
InSeen => LotState::MsgInSeen,
|
||||
OutPreparing => LotState::MsgOutPreparing,
|
||||
OutDraft => LotState::MsgOutDraft,
|
||||
OutPending => LotState::MsgOutPending,
|
||||
OutFailed => LotState::MsgOutFailed,
|
||||
OutDelivered => LotState::MsgOutDelivered,
|
||||
OutMdnRcvd => LotState::MsgOutMdnRcvd,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Summary> for Lot {
|
||||
fn from(summary: Summary) -> Self {
|
||||
Lot::Summary(summary)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Qr> for Lot {
|
||||
fn from(qr: Qr) -> Self {
|
||||
Lot::Qr(qr)
|
||||
}
|
||||
}
|
||||
|
||||
// Make it easy to convert errors into the final `Lot`.
|
||||
impl From<Error> for Lot {
|
||||
fn from(error: Error) -> Self {
|
||||
Lot::Error(error.to_string())
|
||||
}
|
||||
}
|
||||
93
deltachat-ffi/src/providers.rs
Normal file
@@ -0,0 +1,93 @@
|
||||
extern crate deltachat_provider_database;
|
||||
|
||||
use std::ptr;
|
||||
|
||||
use crate::string::{to_string_lossy, StrExt};
|
||||
use deltachat_provider_database::StatusState;
|
||||
|
||||
#[no_mangle]
|
||||
pub type dc_provider_t = deltachat_provider_database::Provider;
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn dc_provider_new_from_domain(
|
||||
domain: *const libc::c_char,
|
||||
) -> *const dc_provider_t {
|
||||
match deltachat_provider_database::get_provider_info(&to_string_lossy(domain)) {
|
||||
Some(provider) => provider,
|
||||
None => ptr::null(),
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn dc_provider_new_from_email(
|
||||
email: *const libc::c_char,
|
||||
) -> *const dc_provider_t {
|
||||
let email = to_string_lossy(email);
|
||||
let domain = deltachat_provider_database::get_domain_from_email(&email);
|
||||
match deltachat_provider_database::get_provider_info(domain) {
|
||||
Some(provider) => provider,
|
||||
None => ptr::null(),
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! null_guard {
|
||||
($context:tt) => {
|
||||
if $context.is_null() {
|
||||
return ptr::null_mut() as *mut libc::c_char;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn dc_provider_get_overview_page(
|
||||
provider: *const dc_provider_t,
|
||||
) -> *mut libc::c_char {
|
||||
null_guard!(provider);
|
||||
format!(
|
||||
"{}/{}",
|
||||
deltachat_provider_database::PROVIDER_OVERVIEW_URL,
|
||||
(*provider).overview_page
|
||||
)
|
||||
.strdup()
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn dc_provider_get_name(provider: *const dc_provider_t) -> *mut libc::c_char {
|
||||
null_guard!(provider);
|
||||
(*provider).name.strdup()
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn dc_provider_get_markdown(
|
||||
provider: *const dc_provider_t,
|
||||
) -> *mut libc::c_char {
|
||||
null_guard!(provider);
|
||||
(*provider).markdown.strdup()
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn dc_provider_get_status_date(
|
||||
provider: *const dc_provider_t,
|
||||
) -> *mut libc::c_char {
|
||||
null_guard!(provider);
|
||||
(*provider).status.date.strdup()
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn dc_provider_get_status(provider: *const dc_provider_t) -> u32 {
|
||||
if provider.is_null() {
|
||||
return 0;
|
||||
}
|
||||
match (*provider).status.state {
|
||||
StatusState::OK => 1,
|
||||
StatusState::PREPARATION => 2,
|
||||
StatusState::BROKEN => 3,
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn dc_provider_unref(_provider: *const dc_provider_t) {
|
||||
()
|
||||
}
|
||||
|
||||
// TODO expose general provider overview url?
|
||||
@@ -1,5 +1,5 @@
|
||||
use failure::Fail;
|
||||
use std::ffi::{CStr, CString};
|
||||
use std::ptr;
|
||||
|
||||
/// Duplicates a string
|
||||
///
|
||||
@@ -8,7 +8,7 @@ use std::ptr;
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust,norun
|
||||
/// use crate::string::{dc_strdup, to_string_lossy};
|
||||
/// use deltachat::dc_tools::{dc_strdup, to_string_lossy};
|
||||
/// unsafe {
|
||||
/// let str_a = b"foobar\x00" as *const u8 as *const libc::c_char;
|
||||
/// let str_a_copy = dc_strdup(str_a);
|
||||
@@ -16,24 +16,27 @@ use std::ptr;
|
||||
/// assert_ne!(str_a, str_a_copy);
|
||||
/// }
|
||||
/// ```
|
||||
unsafe fn dc_strdup(s: *const libc::c_char) -> *mut libc::c_char {
|
||||
let ret: *mut libc::c_char = if !s.is_null() {
|
||||
libc::strdup(s)
|
||||
pub unsafe fn dc_strdup(s: *const libc::c_char) -> *mut libc::c_char {
|
||||
let ret: *mut libc::c_char;
|
||||
if !s.is_null() {
|
||||
ret = libc::strdup(s);
|
||||
assert!(!ret.is_null());
|
||||
} else {
|
||||
libc::calloc(1, 1) as *mut libc::c_char
|
||||
};
|
||||
assert!(!ret.is_null());
|
||||
ret = libc::calloc(1, 1) as *mut libc::c_char;
|
||||
assert!(!ret.is_null());
|
||||
}
|
||||
|
||||
ret
|
||||
}
|
||||
|
||||
/// Error type for the [OsStrExt] trait
|
||||
#[derive(Debug, PartialEq, thiserror::Error)]
|
||||
pub(crate) enum CStringError {
|
||||
#[derive(Debug, Fail, PartialEq)]
|
||||
pub enum CStringError {
|
||||
/// The string contains an interior null byte
|
||||
#[error("String contains an interior null byte")]
|
||||
#[fail(display = "String contains an interior null byte")]
|
||||
InteriorNullByte,
|
||||
/// The string is not valid Unicode
|
||||
#[error("String is not valid unicode")]
|
||||
#[fail(display = "String is not valid unicode")]
|
||||
NotUnicode,
|
||||
}
|
||||
|
||||
@@ -55,14 +58,14 @@ pub(crate) enum CStringError {
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use deltachat::tools::{dc_strdup, OsStrExt};
|
||||
/// use deltachat::dc_tools::{dc_strdup, OsStrExt};
|
||||
/// let path = std::path::Path::new("/some/path");
|
||||
/// let path_c = path.to_c_string().unwrap();
|
||||
/// unsafe {
|
||||
/// let mut c_ptr: *mut libc::c_char = dc_strdup(path_c.as_ptr());
|
||||
/// }
|
||||
/// ```
|
||||
pub(crate) trait OsStrExt {
|
||||
pub trait OsStrExt {
|
||||
/// Convert a [std::ffi::OsStr] to an [std::ffi::CString]
|
||||
///
|
||||
/// This is useful to convert e.g. a [std::path::Path] to
|
||||
@@ -102,9 +105,8 @@ impl<T: AsRef<std::ffi::OsStr>> OsStrExt for T {
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
fn to_c_string(&self) -> Result<CString, CStringError> {
|
||||
use std::os::unix::ffi::OsStrExt;
|
||||
CString::new(self.as_ref().as_bytes()).map_err(|err| {
|
||||
let std::ffi::NulError { .. } = err;
|
||||
CStringError::InteriorNullByte
|
||||
CString::new(self.as_ref().as_bytes()).map_err(|err| match err {
|
||||
std::ffi::NulError { .. } => CStringError::InteriorNullByte,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -120,37 +122,35 @@ fn os_str_to_c_string_unicode(
|
||||
os_str: &dyn AsRef<std::ffi::OsStr>,
|
||||
) -> Result<CString, CStringError> {
|
||||
match os_str.as_ref().to_str() {
|
||||
Some(val) => CString::new(val.as_bytes()).map_err(|err| {
|
||||
let std::ffi::NulError { .. } = err;
|
||||
CStringError::InteriorNullByte
|
||||
Some(val) => CString::new(val.as_bytes()).map_err(|err| match err {
|
||||
std::ffi::NulError { .. } => CStringError::InteriorNullByte,
|
||||
}),
|
||||
None => Err(CStringError::NotUnicode),
|
||||
}
|
||||
}
|
||||
|
||||
/// Convenience methods/associated functions for working with [CString]
|
||||
trait CStringExt {
|
||||
/// Create a new [CString], best effort
|
||||
///
|
||||
/// This is helps transitioning from unsafe code.
|
||||
pub trait CStringExt {
|
||||
/// Create a new [CString], yolo style
|
||||
///
|
||||
/// Like the [to_string_lossy] this doesn't give up in the face of
|
||||
/// bad input (embedded null bytes in this case) instead it does
|
||||
/// the best it can by stripping the embedded null bytes.
|
||||
fn new_lossy<T: Into<Vec<u8>>>(t: T) -> CString {
|
||||
let mut s = t.into();
|
||||
s.retain(|&c| c != 0);
|
||||
CString::new(s).unwrap_or_default()
|
||||
/// This unwrap the result, panicking when there are embedded NULL
|
||||
/// bytes.
|
||||
fn yolo<T: Into<Vec<u8>>>(t: T) -> CString {
|
||||
CString::new(t).expect("String contains null byte, can not be CString")
|
||||
}
|
||||
}
|
||||
|
||||
impl CStringExt for CString {}
|
||||
|
||||
/// Convenience methods to turn strings into C strings.
|
||||
/// Convenience methods to make transitioning from raw C strings easier.
|
||||
///
|
||||
/// To interact with (legacy) C APIs we often need to convert from
|
||||
/// Rust strings to raw C strings. This can be clumsy to do correctly
|
||||
/// and the compiler sometimes allows it in an unsafe way. These
|
||||
/// methods make it more succinct and help you get it right.
|
||||
pub(crate) trait Strdup {
|
||||
pub trait StrExt {
|
||||
/// Allocate a new raw C `*char` version of this string.
|
||||
///
|
||||
/// This allocates a new raw C string which must be freed using
|
||||
@@ -167,64 +167,14 @@ pub(crate) trait Strdup {
|
||||
unsafe fn strdup(&self) -> *mut libc::c_char;
|
||||
}
|
||||
|
||||
impl Strdup for str {
|
||||
impl<T: AsRef<str>> StrExt for T {
|
||||
unsafe fn strdup(&self) -> *mut libc::c_char {
|
||||
let tmp = CString::new_lossy(self);
|
||||
let tmp = CString::yolo(self.as_ref());
|
||||
dc_strdup(tmp.as_ptr())
|
||||
}
|
||||
}
|
||||
|
||||
impl Strdup for String {
|
||||
unsafe fn strdup(&self) -> *mut libc::c_char {
|
||||
let s: &str = self;
|
||||
s.strdup()
|
||||
}
|
||||
}
|
||||
|
||||
impl Strdup for std::path::Path {
|
||||
unsafe fn strdup(&self) -> *mut libc::c_char {
|
||||
let tmp = self.to_c_string().unwrap_or_else(|_| CString::default());
|
||||
dc_strdup(tmp.as_ptr())
|
||||
}
|
||||
}
|
||||
|
||||
impl Strdup for [u8] {
|
||||
unsafe fn strdup(&self) -> *mut libc::c_char {
|
||||
let tmp = CString::new_lossy(self);
|
||||
dc_strdup(tmp.as_ptr())
|
||||
}
|
||||
}
|
||||
|
||||
/// Convenience methods to turn optional strings into C strings.
|
||||
///
|
||||
/// This is the same as the [Strdup] trait but a different trait name
|
||||
/// to work around the type system not allowing to implement [Strdup]
|
||||
/// for `Option<impl Strdup>` When we already have an [Strdup] impl
|
||||
/// for `AsRef<&str>`.
|
||||
///
|
||||
/// When the [Option] is [Option::Some] this behaves just like
|
||||
/// [Strdup::strdup], when it is [Option::None] a null pointer is
|
||||
/// returned.
|
||||
pub(crate) trait OptStrdup {
|
||||
/// Allocate a new raw C `*char` version of this string, or NULL.
|
||||
///
|
||||
/// See [Strdup::strdup] for details.
|
||||
unsafe fn strdup(&self) -> *mut libc::c_char;
|
||||
}
|
||||
|
||||
impl<T: AsRef<str>> OptStrdup for Option<T> {
|
||||
unsafe fn strdup(&self) -> *mut libc::c_char {
|
||||
match self {
|
||||
Some(s) => {
|
||||
let tmp = CString::new_lossy(s.as_ref());
|
||||
dc_strdup(tmp.as_ptr())
|
||||
}
|
||||
None => ptr::null_mut(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn to_string_lossy(s: *const libc::c_char) -> String {
|
||||
pub fn to_string_lossy(s: *const libc::c_char) -> String {
|
||||
if s.is_null() {
|
||||
return "".into();
|
||||
}
|
||||
@@ -234,7 +184,7 @@ pub(crate) fn to_string_lossy(s: *const libc::c_char) -> String {
|
||||
cstr.to_string_lossy().to_string()
|
||||
}
|
||||
|
||||
pub(crate) fn to_opt_string_lossy(s: *const libc::c_char) -> Option<String> {
|
||||
pub fn to_opt_string_lossy(s: *const libc::c_char) -> Option<String> {
|
||||
if s.is_null() {
|
||||
return None;
|
||||
}
|
||||
@@ -250,12 +200,12 @@ pub(crate) fn to_opt_string_lossy(s: *const libc::c_char) -> Option<String> {
|
||||
/// [OsStrExt::to_c_string] requires valid Unicode on Windows, this
|
||||
/// requires that the pointer contains valid UTF-8 on Windows.
|
||||
///
|
||||
/// Because this returns a reference the [Path] slice can not outlive
|
||||
/// Because this returns a reference the [Path] silce can not outlive
|
||||
/// the original pointer.
|
||||
///
|
||||
/// [Path]: std::path::Path
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
pub(crate) fn as_path<'a>(s: *const libc::c_char) -> &'a std::path::Path {
|
||||
pub fn as_path<'a>(s: *const libc::c_char) -> &'a std::path::Path {
|
||||
assert!(!s.is_null(), "cannot be used on null pointers");
|
||||
use std::os::unix::ffi::OsStrExt;
|
||||
unsafe {
|
||||
@@ -267,7 +217,7 @@ pub(crate) fn as_path<'a>(s: *const libc::c_char) -> &'a std::path::Path {
|
||||
|
||||
// as_path() implementation for windows, documented above.
|
||||
#[cfg(target_os = "windows")]
|
||||
pub(crate) fn as_path<'a>(s: *const libc::c_char) -> &'a std::path::Path {
|
||||
pub fn as_path<'a>(s: *const libc::c_char) -> &'a std::path::Path {
|
||||
as_path_unicode(s)
|
||||
}
|
||||
|
||||
@@ -287,9 +237,8 @@ fn as_path_unicode<'a>(s: *const libc::c_char) -> &'a std::path::Path {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use libc::{free, strcmp};
|
||||
|
||||
use super::*;
|
||||
use libc::{free, strcmp};
|
||||
|
||||
#[test]
|
||||
fn test_os_str_to_c_string_cwd() {
|
||||
@@ -375,14 +324,8 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_cstring_new_lossy() {
|
||||
assert!(CString::new("hel\x00lo").is_err());
|
||||
assert!(CString::new(String::from("hel\x00o")).is_err());
|
||||
let r = CString::new("hello").unwrap();
|
||||
assert_eq!(CString::new_lossy("hello"), r);
|
||||
assert_eq!(CString::new_lossy("hel\x00lo"), r);
|
||||
assert_eq!(CString::new_lossy(String::from("hello")), r);
|
||||
assert_eq!(CString::new_lossy(String::from("hel\x00lo")), r);
|
||||
fn test_cstring_yolo() {
|
||||
assert_eq!(CString::new("hello").unwrap(), CString::yolo("hello"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -404,19 +347,4 @@ mod tests {
|
||||
assert_eq!(cmp, 0);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_strdup_opt_string() {
|
||||
unsafe {
|
||||
let s = Some("hello");
|
||||
let c = s.strdup();
|
||||
let cmp = strcmp(c, b"hello\x00" as *const u8 as *const libc::c_char);
|
||||
free(c as *mut libc::c_void);
|
||||
assert_eq!(cmp, 0);
|
||||
|
||||
let s: Option<&str> = None;
|
||||
let c = s.strdup();
|
||||
assert_eq!(c, ptr::null_mut());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
4
deltachat-jsonrpc/.gitignore
vendored
@@ -1,4 +0,0 @@
|
||||
openrpc/openrpc.json
|
||||
accounts/
|
||||
|
||||
.cargo
|
||||
@@ -1,32 +0,0 @@
|
||||
[package]
|
||||
name = "deltachat-jsonrpc"
|
||||
version = "2.48.0-dev"
|
||||
description = "DeltaChat JSON-RPC API"
|
||||
edition = "2021"
|
||||
license = "MPL-2.0"
|
||||
repository = "https://github.com/chatmail/core"
|
||||
|
||||
[dependencies]
|
||||
anyhow = { workspace = true }
|
||||
deltachat = { workspace = true }
|
||||
deltachat-contact-tools = { workspace = true }
|
||||
num-traits = { workspace = true }
|
||||
schemars = "0.8.22"
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
async-channel = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
yerpc = { workspace = true, features = ["anyhow_expose", "openrpc"] }
|
||||
typescript-type-def = { version = "0.5.13", features = ["json_value"] }
|
||||
tokio = { workspace = true }
|
||||
sanitize-filename = { workspace = true }
|
||||
base64 = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
tokio = { workspace = true, features = ["full", "rt-multi-thread"] }
|
||||
tempfile = { workspace = true }
|
||||
futures = { workspace = true }
|
||||
|
||||
|
||||
[features]
|
||||
default = ["vendored"]
|
||||
vendored = ["deltachat/vendored"]
|
||||
@@ -1,71 +0,0 @@
|
||||
# deltachat-jsonrpc
|
||||
|
||||
This crate provides a [JSON-RPC 2.0](https://www.jsonrpc.org/specification) interface to DeltaChat.
|
||||
|
||||
The JSON-RPC API is exposed in two fashions:
|
||||
|
||||
* A executable `deltachat-rpc-server` that exposes the JSON-RPC API through stdio.
|
||||
* The JSON-RPC API can also be called through the [C FFI](../deltachat-ffi). It exposes the functions `dc_jsonrpc_init`, `dc_jsonrpc_request`, `dc_jsonrpc_next_response` and `dc_jsonrpc_unref`. See the docs in the [header file](../deltachat-ffi/deltachat.h) for details.
|
||||
|
||||
We also include a JavaScript and TypeScript client for the JSON-RPC API. The source for this is in the [`typescript`](typescript) folder.
|
||||
|
||||
## Usage
|
||||
|
||||
#### Using the TypeScript/JavaScript client
|
||||
|
||||
The package includes a JavaScript/TypeScript client which is partially auto-generated through the JSON-RPC library used by this crate ([yerpc](https://github.com/chatmail/yerpc)). Find the source in the [`typescript`](typescript) folder.
|
||||
|
||||
To use it locally, first install the dependencies and compile the TypeScript code to JavaScript:
|
||||
```sh
|
||||
cd typescript
|
||||
npm install
|
||||
npm run build
|
||||
```
|
||||
|
||||
The JavaScript client is [published on NPM](https://www.npmjs.com/package/@deltachat/jsonrpc-client).
|
||||
|
||||
A script is included to build autogenerated documentation, which includes all RPC methods:
|
||||
```sh
|
||||
cd typescript
|
||||
npm run docs
|
||||
```
|
||||
Then open the [`typescript/docs`](typescript/docs) folder in a web browser.
|
||||
|
||||
## Development
|
||||
|
||||
#### Running the example app
|
||||
|
||||
### Testing
|
||||
|
||||
The crate includes both a basic Rust smoke test and more featureful integration tests that use the TypeScript client.
|
||||
|
||||
#### Rust tests
|
||||
|
||||
To run the Rust test, use this command:
|
||||
|
||||
```
|
||||
cargo test
|
||||
```
|
||||
|
||||
#### TypeScript tests
|
||||
|
||||
```
|
||||
cd typescript
|
||||
npm run test
|
||||
```
|
||||
|
||||
This will build the `deltachat-jsonrpc-server` binary and then run a test suite.
|
||||
|
||||
The test suite includes some tests that need online connectivity and a way to create test email accounts. To run these tests, set the `CHATMAIL_DOMAIN` environment variable to your testing email server domain.
|
||||
|
||||
```
|
||||
CHATMAIL_DOMAIN=ci-chatmail.testrun.org npm run test
|
||||
```
|
||||
|
||||
#### Test Coverage
|
||||
|
||||
Running `npm run test` will report test coverage. For the coverage to be accurate the online tests need to be run.
|
||||
|
||||
> If you are offline and want to see the coverage results anyway (even though they are inaccurate), you can bypass the errors of the online tests by setting the `COVERAGE_OFFLINE=1` environment variable.
|
||||
|
||||
A summary of the coverage will be reported in the terminal after the test run. Open `coverage/index.html` in a web browser for a detailed report.
|
||||
@@ -1,53 +0,0 @@
|
||||
use anyhow::Result;
|
||||
use deltachat::config::Config;
|
||||
use deltachat::contact::{Contact, ContactId};
|
||||
use serde::Serialize;
|
||||
use typescript_type_def::TypeDef;
|
||||
|
||||
use super::color_int_to_hex_string;
|
||||
|
||||
#[derive(Serialize, TypeDef, schemars::JsonSchema)]
|
||||
#[serde(tag = "kind")]
|
||||
pub enum Account {
|
||||
#[serde(rename_all = "camelCase")]
|
||||
Configured {
|
||||
id: u32,
|
||||
display_name: Option<String>,
|
||||
addr: Option<String>,
|
||||
// size: u32,
|
||||
profile_image: Option<String>,
|
||||
color: String,
|
||||
/// Optional tag as "Work", "Family".
|
||||
/// Meant to help profile owner to differ between profiles with similar names.
|
||||
private_tag: Option<String>,
|
||||
},
|
||||
#[serde(rename_all = "camelCase")]
|
||||
Unconfigured { id: u32 },
|
||||
}
|
||||
|
||||
impl Account {
|
||||
pub async fn from_context(ctx: &deltachat::context::Context, id: u32) -> Result<Self> {
|
||||
if ctx.is_configured().await? {
|
||||
let display_name = ctx.get_config(Config::Displayname).await?;
|
||||
let addr = ctx.get_config(Config::Addr).await?;
|
||||
let profile_image = ctx.get_config(Config::Selfavatar).await?;
|
||||
let color = color_int_to_hex_string(
|
||||
Contact::get_by_id(ctx, ContactId::SELF)
|
||||
.await?
|
||||
.get_or_gen_color(ctx)
|
||||
.await?,
|
||||
);
|
||||
let private_tag = ctx.get_config(Config::PrivateTag).await?;
|
||||
Ok(Account::Configured {
|
||||
id,
|
||||
display_name,
|
||||
addr,
|
||||
profile_image,
|
||||
color,
|
||||
private_tag,
|
||||
})
|
||||
} else {
|
||||
Ok(Account::Unconfigured { id })
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,97 +0,0 @@
|
||||
use anyhow::{Context as _, Result};
|
||||
|
||||
use deltachat::calls::{call_state, CallState};
|
||||
use deltachat::context::Context;
|
||||
use deltachat::message::MsgId;
|
||||
use serde::Serialize;
|
||||
use typescript_type_def::TypeDef;
|
||||
|
||||
#[derive(Serialize, TypeDef, schemars::JsonSchema)]
|
||||
#[serde(rename = "CallInfo", rename_all = "camelCase")]
|
||||
pub struct JsonrpcCallInfo {
|
||||
/// SDP offer.
|
||||
///
|
||||
/// Can be used to manually answer the call
|
||||
/// even if incoming call event was missed.
|
||||
pub sdp_offer: String,
|
||||
|
||||
/// True if the call is started as a video call.
|
||||
pub has_video: bool,
|
||||
|
||||
/// Call state.
|
||||
///
|
||||
/// For example, if the call is accepted, active, canceled, declined etc.
|
||||
pub state: JsonrpcCallState,
|
||||
}
|
||||
|
||||
impl JsonrpcCallInfo {
|
||||
pub async fn from_msg_id(context: &Context, msg_id: MsgId) -> Result<JsonrpcCallInfo> {
|
||||
let call_info = context.load_call_by_id(msg_id).await?.with_context(|| {
|
||||
format!("Attempting to get call state of non-call message {msg_id}")
|
||||
})?;
|
||||
let sdp_offer = call_info.place_call_info.clone();
|
||||
let has_video = call_info.has_video_initially();
|
||||
let state = JsonrpcCallState::from_msg_id(context, msg_id).await?;
|
||||
|
||||
Ok(JsonrpcCallInfo {
|
||||
sdp_offer,
|
||||
has_video,
|
||||
state,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, TypeDef, schemars::JsonSchema)]
|
||||
#[serde(rename = "CallState", tag = "kind")]
|
||||
pub enum JsonrpcCallState {
|
||||
/// Fresh incoming or outgoing call that is still ringing.
|
||||
///
|
||||
/// There is no separate state for outgoing call
|
||||
/// that has been dialled but not ringing on the other side yet
|
||||
/// as we don't know whether the other side received our call.
|
||||
Alerting,
|
||||
|
||||
/// Active call.
|
||||
Active,
|
||||
|
||||
/// Completed call that was once active
|
||||
/// and then was terminated for any reason.
|
||||
Completed {
|
||||
/// Call duration in seconds.
|
||||
duration: i64,
|
||||
},
|
||||
|
||||
/// Incoming call that was not picked up within a timeout
|
||||
/// or was explicitly ended by the caller before we picked up.
|
||||
Missed,
|
||||
|
||||
/// Incoming call that was explicitly ended on our side
|
||||
/// before picking up or outgoing call
|
||||
/// that was declined before the timeout.
|
||||
Declined,
|
||||
|
||||
/// Outgoing call that has been canceled on our side
|
||||
/// before receiving a response.
|
||||
///
|
||||
/// Incoming calls cannot be canceled,
|
||||
/// on the receiver side canceled calls
|
||||
/// usually result in missed calls.
|
||||
Canceled,
|
||||
}
|
||||
|
||||
impl JsonrpcCallState {
|
||||
pub async fn from_msg_id(context: &Context, msg_id: MsgId) -> Result<JsonrpcCallState> {
|
||||
let call_state = call_state(context, msg_id).await?;
|
||||
|
||||
let jsonrpc_call_state = match call_state {
|
||||
CallState::Alerting => JsonrpcCallState::Alerting,
|
||||
CallState::Active => JsonrpcCallState::Active,
|
||||
CallState::Completed { duration } => JsonrpcCallState::Completed { duration },
|
||||
CallState::Missed => JsonrpcCallState::Missed,
|
||||
CallState::Declined => JsonrpcCallState::Declined,
|
||||
CallState::Canceled => JsonrpcCallState::Canceled,
|
||||
};
|
||||
|
||||
Ok(jsonrpc_call_state)
|
||||
}
|
||||
}
|
||||
@@ -1,291 +0,0 @@
|
||||
use std::time::{Duration, SystemTime};
|
||||
|
||||
use anyhow::{bail, Context as _, Result};
|
||||
use deltachat::chat::{self, get_chat_contacts, get_past_chat_contacts, ChatVisibility};
|
||||
use deltachat::chat::{Chat, ChatId};
|
||||
use deltachat::constants::Chattype;
|
||||
use deltachat::contact::{Contact, ContactId};
|
||||
use deltachat::context::Context;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use typescript_type_def::TypeDef;
|
||||
|
||||
use super::color_int_to_hex_string;
|
||||
|
||||
#[derive(Serialize, TypeDef, schemars::JsonSchema)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct FullChat {
|
||||
id: u32,
|
||||
name: String,
|
||||
|
||||
/// True if the chat is encrypted.
|
||||
/// This means that all messages in the chat are encrypted,
|
||||
/// and all contacts in the chat are "key-contacts",
|
||||
/// i.e. identified by the PGP key fingerprint.
|
||||
///
|
||||
/// False if the chat is unencrypted.
|
||||
/// This means that all messages in the chat are unencrypted,
|
||||
/// and all contacts in the chat are "address-contacts",
|
||||
/// i.e. identified by the email address.
|
||||
/// The UI should mark this chat e.g. with a mail-letter icon.
|
||||
///
|
||||
/// Unencrypted groups are called "ad-hoc groups"
|
||||
/// and the user can't add/remove members,
|
||||
/// create a QR invite code,
|
||||
/// or set an avatar.
|
||||
/// These options should therefore be disabled in the UI.
|
||||
///
|
||||
/// Note that it can happen that an encrypted chat
|
||||
/// contains unencrypted messages that were received in core <= v1.159.*
|
||||
/// and vice versa.
|
||||
///
|
||||
/// See also `is_key_contact` on `Contact`.
|
||||
is_encrypted: bool,
|
||||
profile_image: Option<String>, //BLOBS ?
|
||||
archived: bool,
|
||||
pinned: bool,
|
||||
// subtitle - will be moved to frontend because it uses translation functions
|
||||
chat_type: JsonrpcChatType,
|
||||
is_unpromoted: bool,
|
||||
is_self_talk: bool,
|
||||
contact_ids: Vec<u32>,
|
||||
|
||||
/// Contact IDs of the past chat members.
|
||||
past_contact_ids: Vec<u32>,
|
||||
|
||||
color: String,
|
||||
fresh_message_counter: usize,
|
||||
// is_group - please check over chat.type in frontend instead
|
||||
is_contact_request: bool,
|
||||
|
||||
is_device_chat: bool,
|
||||
/// Note that this is different from
|
||||
/// [`ChatListItem::is_self_in_group`](`crate::api::types::chat_list::ChatListItemFetchResult::ChatListItem::is_self_in_group`).
|
||||
/// This property should only be accessed
|
||||
/// when [`FullChat::chat_type`] is [`Chattype::Group`].
|
||||
//
|
||||
// We could utilize [`Chat::is_self_in_chat`],
|
||||
// but that would be an extra DB query.
|
||||
self_in_group: bool,
|
||||
is_muted: bool,
|
||||
ephemeral_timer: u32,
|
||||
can_send: bool,
|
||||
was_seen_recently: bool,
|
||||
mailing_list_address: Option<String>,
|
||||
}
|
||||
|
||||
impl FullChat {
|
||||
pub async fn try_from_dc_chat_id(context: &Context, chat_id: u32) -> Result<Self> {
|
||||
let rust_chat_id = ChatId::new(chat_id);
|
||||
let chat = Chat::load_from_db(context, rust_chat_id).await?;
|
||||
|
||||
let contact_ids = get_chat_contacts(context, rust_chat_id).await?;
|
||||
let past_contact_ids = get_past_chat_contacts(context, rust_chat_id).await?;
|
||||
|
||||
let profile_image = match chat.get_profile_image(context).await? {
|
||||
Some(path_buf) => path_buf.to_str().map(|s| s.to_owned()),
|
||||
None => None,
|
||||
};
|
||||
|
||||
let color = color_int_to_hex_string(chat.get_color(context).await?);
|
||||
let fresh_message_counter = rust_chat_id.get_fresh_msg_cnt(context).await?;
|
||||
let ephemeral_timer = rust_chat_id.get_ephemeral_timer(context).await?.to_u32();
|
||||
|
||||
let can_send = chat.can_send(context).await?;
|
||||
|
||||
let was_seen_recently = if chat.get_type() == Chattype::Single {
|
||||
match contact_ids.first() {
|
||||
Some(contact) => Contact::get_by_id(context, *contact)
|
||||
.await
|
||||
.context("failed to load contact for was_seen_recently")?
|
||||
.was_seen_recently(),
|
||||
None => false,
|
||||
}
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
||||
let mailing_list_address = chat.get_mailinglist_addr().map(|s| s.to_string());
|
||||
|
||||
Ok(FullChat {
|
||||
id: chat_id,
|
||||
name: chat.name.clone(),
|
||||
is_encrypted: chat.is_encrypted(context).await?,
|
||||
profile_image, //BLOBS ?
|
||||
archived: chat.get_visibility() == chat::ChatVisibility::Archived,
|
||||
pinned: chat.get_visibility() == chat::ChatVisibility::Pinned,
|
||||
chat_type: chat.get_type().into(),
|
||||
is_unpromoted: chat.is_unpromoted(),
|
||||
is_self_talk: chat.is_self_talk(),
|
||||
contact_ids: contact_ids.iter().map(|id| id.to_u32()).collect(),
|
||||
past_contact_ids: past_contact_ids.iter().map(|id| id.to_u32()).collect(),
|
||||
color,
|
||||
fresh_message_counter,
|
||||
is_contact_request: chat.is_contact_request(),
|
||||
is_device_chat: chat.is_device_talk(),
|
||||
self_in_group: contact_ids.contains(&ContactId::SELF),
|
||||
is_muted: chat.is_muted(),
|
||||
ephemeral_timer,
|
||||
can_send,
|
||||
was_seen_recently,
|
||||
mailing_list_address,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// cheaper version of fullchat, omits:
|
||||
/// - contact_ids
|
||||
/// - fresh_message_counter
|
||||
/// - ephemeral_timer
|
||||
/// - self_in_group
|
||||
/// - was_seen_recently
|
||||
/// - can_send
|
||||
///
|
||||
/// used when you only need the basic metadata of a chat like type, name, profile picture
|
||||
#[derive(Serialize, TypeDef, schemars::JsonSchema)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct BasicChat {
|
||||
id: u32,
|
||||
name: String,
|
||||
|
||||
/// True if the chat is encrypted.
|
||||
/// This means that all messages in the chat are encrypted,
|
||||
/// and all contacts in the chat are "key-contacts",
|
||||
/// i.e. identified by the PGP key fingerprint.
|
||||
///
|
||||
/// False if the chat is unencrypted.
|
||||
/// This means that all messages in the chat are unencrypted,
|
||||
/// and all contacts in the chat are "address-contacts",
|
||||
/// i.e. identified by the email address.
|
||||
/// The UI should mark this chat e.g. with a mail-letter icon.
|
||||
///
|
||||
/// Unencrypted groups are called "ad-hoc groups"
|
||||
/// and the user can't add/remove members,
|
||||
/// create a QR invite code,
|
||||
/// or set an avatar.
|
||||
/// These options should therefore be disabled in the UI.
|
||||
///
|
||||
/// Note that it can happen that an encrypted chat
|
||||
/// contains unencrypted messages that were received in core <= v1.159.*
|
||||
/// and vice versa.
|
||||
///
|
||||
/// See also `is_key_contact` on `Contact`.
|
||||
is_encrypted: bool,
|
||||
profile_image: Option<String>, //BLOBS ?
|
||||
archived: bool,
|
||||
pinned: bool,
|
||||
chat_type: JsonrpcChatType,
|
||||
is_unpromoted: bool,
|
||||
is_self_talk: bool,
|
||||
color: String,
|
||||
is_contact_request: bool,
|
||||
|
||||
is_device_chat: bool,
|
||||
is_muted: bool,
|
||||
}
|
||||
|
||||
impl BasicChat {
|
||||
pub async fn try_from_dc_chat_id(context: &Context, chat_id: u32) -> Result<Self> {
|
||||
let rust_chat_id = ChatId::new(chat_id);
|
||||
let chat = Chat::load_from_db(context, rust_chat_id).await?;
|
||||
|
||||
let profile_image = match chat.get_profile_image(context).await? {
|
||||
Some(path_buf) => path_buf.to_str().map(|s| s.to_owned()),
|
||||
None => None,
|
||||
};
|
||||
let color = color_int_to_hex_string(chat.get_color(context).await?);
|
||||
|
||||
Ok(BasicChat {
|
||||
id: chat_id,
|
||||
name: chat.name.clone(),
|
||||
is_encrypted: chat.is_encrypted(context).await?,
|
||||
profile_image, //BLOBS ?
|
||||
archived: chat.get_visibility() == chat::ChatVisibility::Archived,
|
||||
pinned: chat.get_visibility() == chat::ChatVisibility::Pinned,
|
||||
chat_type: chat.get_type().into(),
|
||||
is_unpromoted: chat.is_unpromoted(),
|
||||
is_self_talk: chat.is_self_talk(),
|
||||
color,
|
||||
is_contact_request: chat.is_contact_request(),
|
||||
is_device_chat: chat.is_device_talk(),
|
||||
is_muted: chat.is_muted(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize, TypeDef, schemars::JsonSchema)]
|
||||
#[serde(tag = "kind")]
|
||||
pub enum MuteDuration {
|
||||
NotMuted,
|
||||
Forever,
|
||||
Until { duration: i64 },
|
||||
}
|
||||
|
||||
impl MuteDuration {
|
||||
pub fn try_into_core_type(self) -> Result<chat::MuteDuration> {
|
||||
match self {
|
||||
MuteDuration::NotMuted => Ok(chat::MuteDuration::NotMuted),
|
||||
MuteDuration::Forever => Ok(chat::MuteDuration::Forever),
|
||||
MuteDuration::Until { duration } => {
|
||||
if duration <= 0 {
|
||||
bail!("failed to read mute duration")
|
||||
}
|
||||
|
||||
Ok(SystemTime::now()
|
||||
.checked_add(Duration::from_secs(duration as u64))
|
||||
.map_or(chat::MuteDuration::Forever, chat::MuteDuration::Until))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize, TypeDef, schemars::JsonSchema)]
|
||||
#[serde(rename = "ChatVisibility")]
|
||||
pub enum JsonrpcChatVisibility {
|
||||
Normal,
|
||||
Archived,
|
||||
Pinned,
|
||||
}
|
||||
|
||||
impl JsonrpcChatVisibility {
|
||||
pub fn into_core_type(self) -> ChatVisibility {
|
||||
match self {
|
||||
JsonrpcChatVisibility::Normal => ChatVisibility::Normal,
|
||||
JsonrpcChatVisibility::Archived => ChatVisibility::Archived,
|
||||
JsonrpcChatVisibility::Pinned => ChatVisibility::Pinned,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize, PartialEq, TypeDef, schemars::JsonSchema)]
|
||||
#[serde(rename = "ChatType")]
|
||||
pub enum JsonrpcChatType {
|
||||
Single,
|
||||
Group,
|
||||
Mailinglist,
|
||||
OutBroadcast,
|
||||
InBroadcast,
|
||||
}
|
||||
|
||||
impl From<Chattype> for JsonrpcChatType {
|
||||
fn from(chattype: Chattype) -> Self {
|
||||
match chattype {
|
||||
Chattype::Single => JsonrpcChatType::Single,
|
||||
Chattype::Group => JsonrpcChatType::Group,
|
||||
Chattype::Mailinglist => JsonrpcChatType::Mailinglist,
|
||||
Chattype::OutBroadcast => JsonrpcChatType::OutBroadcast,
|
||||
Chattype::InBroadcast => JsonrpcChatType::InBroadcast,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<JsonrpcChatType> for Chattype {
|
||||
fn from(chattype: JsonrpcChatType) -> Self {
|
||||
match chattype {
|
||||
JsonrpcChatType::Single => Chattype::Single,
|
||||
JsonrpcChatType::Group => Chattype::Group,
|
||||
JsonrpcChatType::Mailinglist => Chattype::Mailinglist,
|
||||
JsonrpcChatType::OutBroadcast => Chattype::OutBroadcast,
|
||||
JsonrpcChatType::InBroadcast => Chattype::InBroadcast,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,177 +0,0 @@
|
||||
use anyhow::{Context, Result};
|
||||
use deltachat::chat::{Chat, ChatId};
|
||||
use deltachat::chatlist::get_last_message_for_chat;
|
||||
use deltachat::constants::*;
|
||||
use deltachat::contact::Contact;
|
||||
use deltachat::{
|
||||
chat::{get_chat_contacts, ChatVisibility},
|
||||
chatlist::Chatlist,
|
||||
};
|
||||
use num_traits::cast::ToPrimitive;
|
||||
use serde::Serialize;
|
||||
use typescript_type_def::TypeDef;
|
||||
|
||||
use super::chat::JsonrpcChatType;
|
||||
use super::color_int_to_hex_string;
|
||||
use super::message::MessageViewtype;
|
||||
|
||||
#[derive(Serialize, TypeDef, schemars::JsonSchema)]
|
||||
#[serde(tag = "kind")]
|
||||
pub enum ChatListItemFetchResult {
|
||||
#[serde(rename_all = "camelCase")]
|
||||
ChatListItem {
|
||||
id: u32,
|
||||
name: String,
|
||||
avatar_path: Option<String>,
|
||||
color: String,
|
||||
chat_type: JsonrpcChatType,
|
||||
last_updated: Option<i64>,
|
||||
summary_text1: String,
|
||||
summary_text2: String,
|
||||
summary_status: u32,
|
||||
/// showing preview if last chat message is image
|
||||
summary_preview_image: Option<String>,
|
||||
|
||||
/// True if the chat is encrypted.
|
||||
/// This means that all messages in the chat are encrypted,
|
||||
/// and all contacts in the chat are "key-contacts",
|
||||
/// i.e. identified by the PGP key fingerprint.
|
||||
///
|
||||
/// False if the chat is unencrypted.
|
||||
/// This means that all messages in the chat are unencrypted,
|
||||
/// and all contacts in the chat are "address-contacts",
|
||||
/// i.e. identified by the email address.
|
||||
/// The UI should mark this chat e.g. with a mail-letter icon.
|
||||
///
|
||||
/// Unencrypted groups are called "ad-hoc groups"
|
||||
/// and the user can't add/remove members,
|
||||
/// create a QR invite code,
|
||||
/// or set an avatar.
|
||||
/// These options should therefore be disabled in the UI.
|
||||
///
|
||||
/// Note that it can happen that an encrypted chat
|
||||
/// contains unencrypted messages that were received in core <= v1.159.*
|
||||
/// and vice versa.
|
||||
///
|
||||
/// See also `is_key_contact` on `Contact`.
|
||||
is_encrypted: bool,
|
||||
/// deprecated 2025-07, use chat_type instead
|
||||
is_group: bool,
|
||||
fresh_message_counter: usize,
|
||||
is_self_talk: bool,
|
||||
is_device_talk: bool,
|
||||
is_sending_location: bool,
|
||||
is_self_in_group: bool,
|
||||
is_archived: bool,
|
||||
is_pinned: bool,
|
||||
is_muted: bool,
|
||||
is_contact_request: bool,
|
||||
/// contact id if this is a dm chat (for view profile entry in context menu)
|
||||
dm_chat_contact: Option<u32>,
|
||||
was_seen_recently: bool,
|
||||
last_message_type: Option<MessageViewtype>,
|
||||
last_message_id: Option<u32>,
|
||||
},
|
||||
#[serde(rename_all = "camelCase")]
|
||||
ArchiveLink { fresh_message_counter: usize },
|
||||
#[serde(rename_all = "camelCase")]
|
||||
Error { id: u32, error: String },
|
||||
}
|
||||
|
||||
pub(crate) async fn get_chat_list_item_by_id(
|
||||
ctx: &deltachat::context::Context,
|
||||
entry: u32,
|
||||
) -> Result<ChatListItemFetchResult> {
|
||||
let chat_id = ChatId::new(entry);
|
||||
let fresh_message_counter = chat_id.get_fresh_msg_cnt(ctx).await?;
|
||||
|
||||
if chat_id.is_archived_link() {
|
||||
return Ok(ChatListItemFetchResult::ArchiveLink {
|
||||
fresh_message_counter,
|
||||
});
|
||||
}
|
||||
|
||||
let last_msgid = get_last_message_for_chat(ctx, chat_id).await?;
|
||||
|
||||
let chat = Chat::load_from_db(ctx, chat_id).await.context("chat")?;
|
||||
let summary = Chatlist::get_summary2(ctx, chat_id, last_msgid, Some(&chat))
|
||||
.await
|
||||
.context("summary")?;
|
||||
|
||||
let summary_text1 = summary.prefix.map_or_else(String::new, |s| s.to_string());
|
||||
let summary_text2 = summary.text.to_owned();
|
||||
|
||||
let summary_preview_image = summary.thumbnail_path;
|
||||
|
||||
let visibility = chat.get_visibility();
|
||||
|
||||
let avatar_path = chat
|
||||
.get_profile_image(ctx)
|
||||
.await?
|
||||
.map(|path| path.to_str().unwrap_or("invalid/path").to_owned());
|
||||
|
||||
let (last_updated, message_type) = match last_msgid {
|
||||
Some(id) => {
|
||||
if let Some(last_message) =
|
||||
deltachat::message::Message::load_from_db_optional(ctx, id).await?
|
||||
{
|
||||
(
|
||||
Some(last_message.get_timestamp() * 1000),
|
||||
Some(last_message.get_viewtype().into()),
|
||||
)
|
||||
} else {
|
||||
// Message may be deleted by the time we try to load it.
|
||||
(None, None)
|
||||
}
|
||||
}
|
||||
None => (None, None),
|
||||
};
|
||||
|
||||
let (dm_chat_contact, was_seen_recently) = if chat.get_type() == Chattype::Single {
|
||||
let chat_contacts = get_chat_contacts(ctx, chat_id).await?;
|
||||
let contact = chat_contacts.first();
|
||||
let was_seen_recently = match contact {
|
||||
Some(contact) => Contact::get_by_id(ctx, *contact)
|
||||
.await
|
||||
.context("contact")?
|
||||
.was_seen_recently(),
|
||||
None => false,
|
||||
};
|
||||
(
|
||||
contact.map(|contact_id| contact_id.to_u32()),
|
||||
was_seen_recently,
|
||||
)
|
||||
} else {
|
||||
(None, false)
|
||||
};
|
||||
|
||||
let color = color_int_to_hex_string(chat.get_color(ctx).await?);
|
||||
|
||||
Ok(ChatListItemFetchResult::ChatListItem {
|
||||
id: chat_id.to_u32(),
|
||||
name: chat.get_name().to_owned(),
|
||||
avatar_path,
|
||||
color,
|
||||
chat_type: chat.get_type().into(),
|
||||
last_updated,
|
||||
summary_text1,
|
||||
summary_text2,
|
||||
summary_status: summary.state.to_u32().expect("impossible"), // idea and a function to transform the constant to strings? or return string enum
|
||||
summary_preview_image,
|
||||
is_encrypted: chat.is_encrypted(ctx).await?,
|
||||
is_group: chat.get_type() == Chattype::Group,
|
||||
fresh_message_counter,
|
||||
is_self_talk: chat.is_self_talk(),
|
||||
is_device_talk: chat.is_device_talk(),
|
||||
is_self_in_group: chat.is_self_in_chat(ctx).await?,
|
||||
is_sending_location: chat.is_sending_locations(),
|
||||
is_archived: visibility == ChatVisibility::Archived,
|
||||
is_pinned: visibility == ChatVisibility::Pinned,
|
||||
is_muted: chat.is_muted(),
|
||||
is_contact_request: chat.is_contact_request(),
|
||||
dm_chat_contact,
|
||||
was_seen_recently,
|
||||
last_message_type: message_type,
|
||||
last_message_id: last_msgid.map(|id| id.to_u32()),
|
||||
})
|
||||
}
|
||||
@@ -1,148 +0,0 @@
|
||||
use anyhow::Result;
|
||||
use deltachat::context::Context;
|
||||
use deltachat::key::{DcKey, SignedPublicKey};
|
||||
use serde::Serialize;
|
||||
use typescript_type_def::TypeDef;
|
||||
|
||||
use super::color_int_to_hex_string;
|
||||
|
||||
#[derive(Serialize, TypeDef, schemars::JsonSchema)]
|
||||
#[serde(rename = "Contact", rename_all = "camelCase")]
|
||||
pub struct ContactObject {
|
||||
address: String,
|
||||
color: String,
|
||||
auth_name: String,
|
||||
status: String,
|
||||
display_name: String,
|
||||
id: u32,
|
||||
name: String,
|
||||
profile_image: Option<String>, // BLOBS
|
||||
name_and_addr: String,
|
||||
is_blocked: bool,
|
||||
|
||||
/// Is the contact a key contact.
|
||||
is_key_contact: bool,
|
||||
|
||||
/// Is encryption available for this contact.
|
||||
///
|
||||
/// This can only be true for key-contacts.
|
||||
/// However, it is possible to have a key-contact
|
||||
/// for which encryption is not available because we don't have a key yet,
|
||||
/// e.g. if we just scanned the fingerprint from a QR code.
|
||||
e2ee_avail: bool,
|
||||
|
||||
/// True if the contact
|
||||
/// can be added to protected chats
|
||||
/// because SELF and contact have verified their fingerprints in both directions.
|
||||
///
|
||||
/// See [`Self::verifier_id`]/`Contact.verifierId` for a guidance how to display these information.
|
||||
is_verified: bool,
|
||||
|
||||
/// The contact ID that verified a contact.
|
||||
///
|
||||
/// As verifier may be unknown,
|
||||
/// use [`Self::is_verified`]/`Contact.isVerified` to check if a contact can be added to a protected chat.
|
||||
///
|
||||
/// UI should display the information in the contact's profile as follows:
|
||||
///
|
||||
/// - If `verifierId` != 0,
|
||||
/// display text "Introduced by ..."
|
||||
/// with the name of the contact.
|
||||
/// Prefix the text by a green checkmark.
|
||||
///
|
||||
/// - If `verifierId` == 0 and `isVerified` != 0,
|
||||
/// display "Introduced" prefixed by a green checkmark.
|
||||
///
|
||||
/// - if `verifierId` == 0 and `isVerified` == 0,
|
||||
/// display nothing
|
||||
///
|
||||
/// This contains the contact ID of the verifier.
|
||||
/// If it is `DC_CONTACT_ID_SELF`, we verified the contact ourself.
|
||||
/// If it is None/Null, we don't have verifier information or
|
||||
/// the contact is not verified.
|
||||
verifier_id: Option<u32>,
|
||||
|
||||
/// the contact's last seen timestamp
|
||||
last_seen: i64,
|
||||
was_seen_recently: bool,
|
||||
|
||||
/// If the contact is a bot.
|
||||
is_bot: bool,
|
||||
}
|
||||
|
||||
impl ContactObject {
|
||||
pub async fn try_from_dc_contact(
|
||||
context: &Context,
|
||||
contact: deltachat::contact::Contact,
|
||||
) -> Result<Self> {
|
||||
let profile_image = match contact.get_profile_image(context).await? {
|
||||
Some(path_buf) => path_buf.to_str().map(|s| s.to_owned()),
|
||||
None => None,
|
||||
};
|
||||
let is_verified = contact.is_verified(context).await?;
|
||||
|
||||
let verifier_id = contact
|
||||
.get_verifier_id(context)
|
||||
.await?
|
||||
.flatten()
|
||||
.map(|contact_id| contact_id.to_u32());
|
||||
|
||||
Ok(ContactObject {
|
||||
address: contact.get_addr().to_owned(),
|
||||
color: color_int_to_hex_string(contact.get_color()),
|
||||
auth_name: contact.get_authname().to_owned(),
|
||||
status: contact.get_status().to_owned(),
|
||||
display_name: contact.get_display_name().to_owned(),
|
||||
id: contact.id.to_u32(),
|
||||
name: contact.get_name().to_owned(),
|
||||
profile_image, //BLOBS
|
||||
name_and_addr: contact.get_name_n_addr(),
|
||||
is_blocked: contact.is_blocked(),
|
||||
is_key_contact: contact.is_key_contact(),
|
||||
e2ee_avail: contact.e2ee_avail(context).await?,
|
||||
is_verified,
|
||||
verifier_id,
|
||||
last_seen: contact.last_seen(),
|
||||
was_seen_recently: contact.was_seen_recently(),
|
||||
is_bot: contact.is_bot(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, TypeDef, schemars::JsonSchema)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct VcardContact {
|
||||
/// Email address.
|
||||
addr: String,
|
||||
/// The contact's name, or the email address if no name was given.
|
||||
display_name: String,
|
||||
/// Public PGP key in Base64.
|
||||
key: Option<String>,
|
||||
/// Profile image in Base64.
|
||||
profile_image: Option<String>,
|
||||
/// Contact color as hex string.
|
||||
color: String,
|
||||
/// Last update timestamp.
|
||||
timestamp: Option<i64>,
|
||||
}
|
||||
|
||||
impl From<deltachat_contact_tools::VcardContact> for VcardContact {
|
||||
fn from(vc: deltachat_contact_tools::VcardContact) -> Self {
|
||||
let display_name = vc.display_name().to_string();
|
||||
let is_self = false;
|
||||
let fpr = vc.key.as_deref().and_then(|k| {
|
||||
SignedPublicKey::from_base64(k)
|
||||
.ok()
|
||||
.map(|k| k.dc_fingerprint())
|
||||
});
|
||||
let color = deltachat::contact::get_color(is_self, &vc.addr, &fpr);
|
||||
Self {
|
||||
addr: vc.addr,
|
||||
display_name,
|
||||
key: vc.key,
|
||||
profile_image: vc.profile_image,
|
||||
color: color_int_to_hex_string(color),
|
||||
timestamp: vc.timestamp.ok(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,668 +0,0 @@
|
||||
use deltachat::{Event as CoreEvent, EventType as CoreEventType};
|
||||
use serde::Serialize;
|
||||
use typescript_type_def::TypeDef;
|
||||
|
||||
use super::chat::JsonrpcChatType;
|
||||
|
||||
#[derive(Serialize, TypeDef, schemars::JsonSchema)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Event {
|
||||
/// Event payload.
|
||||
event: EventType,
|
||||
|
||||
/// Account ID.
|
||||
context_id: u32,
|
||||
}
|
||||
|
||||
impl From<CoreEvent> for Event {
|
||||
fn from(event: CoreEvent) -> Self {
|
||||
Event {
|
||||
event: event.typ.into(),
|
||||
context_id: event.id,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, TypeDef, schemars::JsonSchema)]
|
||||
#[serde(tag = "kind")]
|
||||
pub enum EventType {
|
||||
/// The library-user may write an informational string to the log.
|
||||
///
|
||||
/// This event should *not* be reported to the end-user using a popup or something like
|
||||
/// that.
|
||||
Info { msg: String },
|
||||
|
||||
/// Emitted when SMTP connection is established and login was successful.
|
||||
SmtpConnected { msg: String },
|
||||
|
||||
/// Emitted when IMAP connection is established and login was successful.
|
||||
ImapConnected { msg: String },
|
||||
|
||||
/// Emitted when a message was successfully sent to the SMTP server.
|
||||
SmtpMessageSent { msg: String },
|
||||
|
||||
/// Emitted when an IMAP message has been marked as deleted
|
||||
ImapMessageDeleted { msg: String },
|
||||
|
||||
/// Emitted when an IMAP message has been moved
|
||||
ImapMessageMoved { msg: String },
|
||||
|
||||
/// Emitted before going into IDLE on the Inbox folder.
|
||||
ImapInboxIdle,
|
||||
|
||||
/// Emitted when an new file in the $BLOBDIR was created
|
||||
NewBlobFile { file: String },
|
||||
|
||||
/// Emitted when an file in the $BLOBDIR was deleted
|
||||
DeletedBlobFile { file: String },
|
||||
|
||||
/// The library-user should write a warning string to the log.
|
||||
///
|
||||
/// This event should *not* be reported to the end-user using a popup or something like
|
||||
/// that.
|
||||
Warning { msg: String },
|
||||
|
||||
/// The library-user should report an error to the end-user.
|
||||
///
|
||||
/// As most things are asynchronous, things may go wrong at any time and the user
|
||||
/// should not be disturbed by a dialog or so. Instead, use a bubble or so.
|
||||
///
|
||||
/// However, for ongoing processes (eg. configure())
|
||||
/// or for functions that are expected to fail (eg. autocryptContinueKeyTransfer())
|
||||
/// it might be better to delay showing these events until the function has really
|
||||
/// failed (returned false). It should be sufficient to report only the *last* error
|
||||
/// in a message box then.
|
||||
Error { msg: String },
|
||||
|
||||
/// An action cannot be performed because the user is not in the group.
|
||||
/// Reported eg. after a call to
|
||||
/// setChatName(), setChatProfileImage(),
|
||||
/// addContactToChat(), removeContactFromChat(),
|
||||
/// and messages sending functions.
|
||||
ErrorSelfNotInGroup { msg: String },
|
||||
|
||||
/// Messages or chats changed. One or more messages or chats changed for various
|
||||
/// reasons in the database:
|
||||
/// - Messages sent, received or removed
|
||||
/// - Chats created, deleted or archived
|
||||
/// - A draft has been set
|
||||
#[serde(rename_all = "camelCase")]
|
||||
MsgsChanged {
|
||||
/// Set if only a single chat is affected by the changes, otherwise 0.
|
||||
chat_id: u32,
|
||||
|
||||
/// Set if only a single message is affected by the changes, otherwise 0.
|
||||
msg_id: u32,
|
||||
},
|
||||
|
||||
/// Reactions for the message changed.
|
||||
#[serde(rename_all = "camelCase")]
|
||||
ReactionsChanged {
|
||||
/// ID of the chat which the message belongs to.
|
||||
chat_id: u32,
|
||||
|
||||
/// ID of the message for which reactions were changed.
|
||||
msg_id: u32,
|
||||
|
||||
/// ID of the contact whose reaction set is changed.
|
||||
contact_id: u32,
|
||||
},
|
||||
|
||||
/// A reaction to one's own sent message received.
|
||||
/// Typically, the UI will show a notification for that.
|
||||
///
|
||||
/// In addition to this event, ReactionsChanged is emitted.
|
||||
#[serde(rename_all = "camelCase")]
|
||||
IncomingReaction {
|
||||
/// ID of the chat which the message belongs to.
|
||||
chat_id: u32,
|
||||
|
||||
/// ID of the contact whose reaction set is changed.
|
||||
contact_id: u32,
|
||||
|
||||
/// ID of the message for which reactions were changed.
|
||||
msg_id: u32,
|
||||
|
||||
/// The reaction.
|
||||
reaction: String,
|
||||
},
|
||||
|
||||
/// Incoming webxdc info or summary update, should be notified.
|
||||
#[serde(rename_all = "camelCase")]
|
||||
IncomingWebxdcNotify {
|
||||
/// ID of the chat.
|
||||
chat_id: u32,
|
||||
|
||||
/// ID of the contact sending.
|
||||
contact_id: u32,
|
||||
|
||||
/// ID of the added info message or webxdc instance in case of summary change.
|
||||
msg_id: u32,
|
||||
|
||||
/// Text to notify.
|
||||
text: String,
|
||||
|
||||
/// Link assigned to this notification, if any.
|
||||
href: Option<String>,
|
||||
},
|
||||
|
||||
/// There is a fresh message. Typically, the user will show a notification
|
||||
/// when receiving this message.
|
||||
///
|
||||
/// There is no extra #DC_EVENT_MSGS_CHANGED event sent together with this event.
|
||||
#[serde(rename_all = "camelCase")]
|
||||
IncomingMsg {
|
||||
/// ID of the chat where the message is assigned.
|
||||
chat_id: u32,
|
||||
|
||||
/// ID of the message.
|
||||
msg_id: u32,
|
||||
},
|
||||
|
||||
/// Downloading a bunch of messages just finished. This is an
|
||||
/// event to allow the UI to only show one notification per message bunch,
|
||||
/// instead of cluttering the user with many notifications.
|
||||
#[serde(rename_all = "camelCase")]
|
||||
IncomingMsgBunch,
|
||||
|
||||
/// Messages were seen or noticed.
|
||||
/// chat id is always set.
|
||||
#[serde(rename_all = "camelCase")]
|
||||
MsgsNoticed { chat_id: u32 },
|
||||
|
||||
/// A single message is sent successfully. State changed from DC_STATE_OUT_PENDING to
|
||||
/// DC_STATE_OUT_DELIVERED, see `Message.state`.
|
||||
#[serde(rename_all = "camelCase")]
|
||||
MsgDelivered {
|
||||
/// ID of the chat which the message belongs to.
|
||||
chat_id: u32,
|
||||
|
||||
/// ID of the message that was successfully sent.
|
||||
msg_id: u32,
|
||||
},
|
||||
|
||||
/// A single message could not be sent. State changed from DC_STATE_OUT_PENDING or DC_STATE_OUT_DELIVERED to
|
||||
/// DC_STATE_OUT_FAILED, see `Message.state`.
|
||||
#[serde(rename_all = "camelCase")]
|
||||
MsgFailed {
|
||||
/// ID of the chat which the message belongs to.
|
||||
chat_id: u32,
|
||||
|
||||
/// ID of the message that could not be sent.
|
||||
msg_id: u32,
|
||||
},
|
||||
|
||||
/// A single message is read by the receiver. State changed from DC_STATE_OUT_DELIVERED to
|
||||
/// DC_STATE_OUT_MDN_RCVD, see `Message.state`.
|
||||
#[serde(rename_all = "camelCase")]
|
||||
MsgRead {
|
||||
/// ID of the chat which the message belongs to.
|
||||
chat_id: u32,
|
||||
|
||||
/// ID of the message that was read.
|
||||
msg_id: u32,
|
||||
},
|
||||
|
||||
/// A single message was deleted.
|
||||
///
|
||||
/// This event means that the message will no longer appear in the messagelist.
|
||||
/// UI should remove the message from the messagelist
|
||||
/// in response to this event if the message is currently displayed.
|
||||
///
|
||||
/// The message may have been explicitly deleted by the user or expired.
|
||||
/// Internally the message may have been removed from the database,
|
||||
/// moved to the trash chat or hidden.
|
||||
///
|
||||
/// This event does not indicate the message
|
||||
/// deletion from the server.
|
||||
#[serde(rename_all = "camelCase")]
|
||||
MsgDeleted {
|
||||
/// ID of the chat where the message was prior to deletion.
|
||||
/// Never 0.
|
||||
chat_id: u32,
|
||||
|
||||
/// ID of the deleted message. Never 0.
|
||||
msg_id: u32,
|
||||
},
|
||||
|
||||
/// Chat changed. The name or the image of a chat group was changed or members were added or removed.
|
||||
/// See setChatName(), setChatProfileImage(), addContactToChat()
|
||||
/// and removeContactFromChat().
|
||||
///
|
||||
/// This event does not include ephemeral timer modification, which
|
||||
/// is a separate event.
|
||||
#[serde(rename_all = "camelCase")]
|
||||
ChatModified { chat_id: u32 },
|
||||
|
||||
/// Chat ephemeral timer changed.
|
||||
#[serde(rename_all = "camelCase")]
|
||||
ChatEphemeralTimerModified {
|
||||
/// Chat ID.
|
||||
chat_id: u32,
|
||||
|
||||
/// New ephemeral timer value.
|
||||
timer: u32,
|
||||
},
|
||||
|
||||
/// Chat deleted.
|
||||
ChatDeleted {
|
||||
/// Chat ID.
|
||||
chat_id: u32,
|
||||
},
|
||||
|
||||
/// Contact(s) created, renamed, blocked or deleted.
|
||||
#[serde(rename_all = "camelCase")]
|
||||
ContactsChanged {
|
||||
/// If set, this is the contact_id of an added contact that should be selected.
|
||||
contact_id: Option<u32>,
|
||||
},
|
||||
|
||||
/// Location of one or more contact has changed.
|
||||
#[serde(rename_all = "camelCase")]
|
||||
LocationChanged {
|
||||
/// contact_id of the contact for which the location has changed.
|
||||
/// If the locations of several contacts have been changed,
|
||||
/// this parameter is set to `None`.
|
||||
contact_id: Option<u32>,
|
||||
},
|
||||
|
||||
/// Inform about the configuration progress started by configure().
|
||||
ConfigureProgress {
|
||||
/// Progress.
|
||||
///
|
||||
/// 0=error, 1-999=progress in permille, 1000=success and done
|
||||
progress: u16,
|
||||
|
||||
/// Progress comment or error, something to display to the user.
|
||||
comment: Option<String>,
|
||||
},
|
||||
|
||||
/// Inform about the import/export progress started by imex().
|
||||
///
|
||||
#[serde(rename_all = "camelCase")]
|
||||
ImexProgress {
|
||||
/// 0=error, 1-999=progress in permille, 1000=success and done
|
||||
progress: u16,
|
||||
},
|
||||
|
||||
/// A file has been exported. A file has been written by imex().
|
||||
/// This event may be sent multiple times by a single call to imex().
|
||||
///
|
||||
/// A typical purpose for a handler of this event may be to make the file public to some system
|
||||
/// services.
|
||||
///
|
||||
/// @param data2 0
|
||||
#[serde(rename_all = "camelCase")]
|
||||
ImexFileWritten { path: String },
|
||||
|
||||
/// Progress event sent when SecureJoin protocol has finished
|
||||
/// from the view of the inviter (Alice, the person who shows the QR code).
|
||||
///
|
||||
/// These events are typically sent after a joiner has scanned the QR code
|
||||
/// generated by getChatSecurejoinQrCodeSvg().
|
||||
#[serde(rename_all = "camelCase")]
|
||||
SecurejoinInviterProgress {
|
||||
/// ID of the contact that wants to join.
|
||||
contact_id: u32,
|
||||
|
||||
/// The type of the joined chat.
|
||||
/// This can take the same values
|
||||
/// as `BasicChat.chatType` ([`crate::api::types::chat::BasicChat::chat_type`]).
|
||||
chat_type: JsonrpcChatType,
|
||||
/// ID of the chat in case of success.
|
||||
chat_id: u32,
|
||||
|
||||
/// Progress, always 1000.
|
||||
progress: u16,
|
||||
},
|
||||
|
||||
/// Progress information of a secure-join handshake from the view of the joiner
|
||||
/// (Bob, the person who scans the QR code).
|
||||
/// The events are typically sent while secureJoin(), which
|
||||
/// may take some time, is executed.
|
||||
#[serde(rename_all = "camelCase")]
|
||||
SecurejoinJoinerProgress {
|
||||
/// ID of the inviting contact.
|
||||
contact_id: u32,
|
||||
|
||||
/// Progress as:
|
||||
/// 400=vg-/vc-request-with-auth sent, typically shown as "alice@addr verified, introducing myself."
|
||||
/// (Bob has verified alice and waits until Alice does the same for him)
|
||||
/// 1000=vg-member-added/vc-contact-confirm received
|
||||
progress: u16,
|
||||
},
|
||||
|
||||
/// The connectivity to the server changed.
|
||||
/// This means that you should refresh the connectivity view
|
||||
/// and possibly the connectivtiy HTML; see getConnectivity() and
|
||||
/// getConnectivityHtml() for details.
|
||||
ConnectivityChanged,
|
||||
|
||||
/// Deprecated by `ConfigSynced`.
|
||||
SelfavatarChanged,
|
||||
|
||||
/// A multi-device synced config value changed. Maybe the app needs to refresh smth. For
|
||||
/// uniformity this is emitted on the source device too. The value isn't here, otherwise it
|
||||
/// would be logged which might not be good for privacy.
|
||||
ConfigSynced {
|
||||
/// Configuration key.
|
||||
key: String,
|
||||
},
|
||||
|
||||
#[serde(rename_all = "camelCase")]
|
||||
WebxdcStatusUpdate {
|
||||
/// Message ID.
|
||||
msg_id: u32,
|
||||
|
||||
/// Status update ID.
|
||||
status_update_serial: u32,
|
||||
},
|
||||
|
||||
/// Data received over an ephemeral peer channel.
|
||||
#[serde(rename_all = "camelCase")]
|
||||
WebxdcRealtimeData {
|
||||
/// Message ID.
|
||||
msg_id: u32,
|
||||
|
||||
/// Realtime data.
|
||||
data: Vec<u8>,
|
||||
},
|
||||
|
||||
/// Advertisement received over an ephemeral peer channel.
|
||||
/// This can be used by bots to initiate peer-to-peer communication from their side.
|
||||
#[serde(rename_all = "camelCase")]
|
||||
WebxdcRealtimeAdvertisementReceived {
|
||||
/// Message ID of the webxdc instance.
|
||||
msg_id: u32,
|
||||
},
|
||||
|
||||
/// Inform that a message containing a webxdc instance has been deleted
|
||||
#[serde(rename_all = "camelCase")]
|
||||
WebxdcInstanceDeleted {
|
||||
/// ID of the deleted message.
|
||||
msg_id: u32,
|
||||
},
|
||||
|
||||
/// Tells that the Background fetch was completed (or timed out).
|
||||
/// This event acts as a marker, when you reach this event you can be sure
|
||||
/// that all events emitted during the background fetch were processed.
|
||||
///
|
||||
/// This event is only emitted by the account manager
|
||||
AccountsBackgroundFetchDone,
|
||||
/// Inform that set of chats or the order of the chats in the chatlist has changed.
|
||||
///
|
||||
/// Sometimes this is emitted together with `UIChatlistItemChanged`.
|
||||
ChatlistChanged,
|
||||
|
||||
/// Inform that a single chat list item changed and needs to be rerendered.
|
||||
/// If `chat_id` is set to None, then all currently visible chats need to be rerendered, and all not-visible items need to be cleared from cache if the UI has a cache.
|
||||
#[serde(rename_all = "camelCase")]
|
||||
ChatlistItemChanged {
|
||||
/// ID of the changed chat
|
||||
chat_id: Option<u32>,
|
||||
},
|
||||
|
||||
/// Inform that the list of accounts has changed (an account removed or added or (not yet implemented) the account order changes)
|
||||
///
|
||||
/// This event is only emitted by the account manager
|
||||
AccountsChanged,
|
||||
|
||||
/// Inform that an account property that might be shown in the account list changed, namely:
|
||||
/// - is_configured (see is_configured())
|
||||
/// - displayname
|
||||
/// - selfavatar
|
||||
/// - private_tag
|
||||
///
|
||||
/// This event is emitted from the account whose property changed.
|
||||
AccountsItemChanged,
|
||||
|
||||
/// Inform than some events have been skipped due to event channel overflow.
|
||||
EventChannelOverflow {
|
||||
/// Number of events skipped.
|
||||
n: u64,
|
||||
},
|
||||
|
||||
/// Incoming call.
|
||||
IncomingCall {
|
||||
/// ID of the info message referring to the call.
|
||||
msg_id: u32,
|
||||
/// ID of the chat which the message belongs to.
|
||||
chat_id: u32,
|
||||
/// User-defined info as passed to place_outgoing_call()
|
||||
place_call_info: String,
|
||||
/// True if incoming call is a video call.
|
||||
has_video: bool,
|
||||
},
|
||||
|
||||
/// Incoming call accepted.
|
||||
/// This is esp. interesting to stop ringing on other devices.
|
||||
IncomingCallAccepted {
|
||||
/// ID of the info message referring to the call.
|
||||
msg_id: u32,
|
||||
/// ID of the chat which the message belongs to.
|
||||
chat_id: u32,
|
||||
/// The call was accepted from this device (process).
|
||||
from_this_device: bool,
|
||||
},
|
||||
|
||||
/// Outgoing call accepted.
|
||||
OutgoingCallAccepted {
|
||||
/// ID of the info message referring to the call.
|
||||
msg_id: u32,
|
||||
/// ID of the chat which the message belongs to.
|
||||
chat_id: u32,
|
||||
/// User-defined info passed to dc_accept_incoming_call(
|
||||
accept_call_info: String,
|
||||
},
|
||||
|
||||
/// Call ended.
|
||||
CallEnded {
|
||||
/// ID of the info message referring to the call.
|
||||
msg_id: u32,
|
||||
/// ID of the chat which the message belongs to.
|
||||
chat_id: u32,
|
||||
},
|
||||
|
||||
/// One or more transports has changed.
|
||||
///
|
||||
/// UI should update the list.
|
||||
///
|
||||
/// This event is emitted when transport
|
||||
/// synchronization messages arrives,
|
||||
/// but not when the UI modifies the transport list by itself.
|
||||
TransportsModified,
|
||||
}
|
||||
|
||||
impl From<CoreEventType> for EventType {
|
||||
fn from(event: CoreEventType) -> Self {
|
||||
use EventType::*;
|
||||
match event {
|
||||
CoreEventType::Info(msg) => Info { msg },
|
||||
CoreEventType::SmtpConnected(msg) => SmtpConnected { msg },
|
||||
CoreEventType::ImapConnected(msg) => ImapConnected { msg },
|
||||
CoreEventType::SmtpMessageSent(msg) => SmtpMessageSent { msg },
|
||||
CoreEventType::ImapMessageDeleted(msg) => ImapMessageDeleted { msg },
|
||||
CoreEventType::ImapMessageMoved(msg) => ImapMessageMoved { msg },
|
||||
CoreEventType::ImapInboxIdle => ImapInboxIdle,
|
||||
CoreEventType::NewBlobFile(file) => NewBlobFile { file },
|
||||
CoreEventType::DeletedBlobFile(file) => DeletedBlobFile { file },
|
||||
CoreEventType::Warning(msg) => Warning { msg },
|
||||
CoreEventType::Error(msg) => Error { msg },
|
||||
CoreEventType::ErrorSelfNotInGroup(msg) => ErrorSelfNotInGroup { msg },
|
||||
CoreEventType::MsgsChanged { chat_id, msg_id } => MsgsChanged {
|
||||
chat_id: chat_id.to_u32(),
|
||||
msg_id: msg_id.to_u32(),
|
||||
},
|
||||
CoreEventType::ReactionsChanged {
|
||||
chat_id,
|
||||
msg_id,
|
||||
contact_id,
|
||||
} => ReactionsChanged {
|
||||
chat_id: chat_id.to_u32(),
|
||||
msg_id: msg_id.to_u32(),
|
||||
contact_id: contact_id.to_u32(),
|
||||
},
|
||||
CoreEventType::IncomingReaction {
|
||||
chat_id,
|
||||
contact_id,
|
||||
msg_id,
|
||||
reaction,
|
||||
} => IncomingReaction {
|
||||
chat_id: chat_id.to_u32(),
|
||||
contact_id: contact_id.to_u32(),
|
||||
msg_id: msg_id.to_u32(),
|
||||
reaction: reaction.as_str().to_string(),
|
||||
},
|
||||
CoreEventType::IncomingWebxdcNotify {
|
||||
chat_id,
|
||||
contact_id,
|
||||
msg_id,
|
||||
text,
|
||||
href,
|
||||
} => IncomingWebxdcNotify {
|
||||
chat_id: chat_id.to_u32(),
|
||||
contact_id: contact_id.to_u32(),
|
||||
msg_id: msg_id.to_u32(),
|
||||
text,
|
||||
href,
|
||||
},
|
||||
CoreEventType::IncomingMsg { chat_id, msg_id } => IncomingMsg {
|
||||
chat_id: chat_id.to_u32(),
|
||||
msg_id: msg_id.to_u32(),
|
||||
},
|
||||
CoreEventType::IncomingMsgBunch => IncomingMsgBunch,
|
||||
CoreEventType::MsgsNoticed(chat_id) => MsgsNoticed {
|
||||
chat_id: chat_id.to_u32(),
|
||||
},
|
||||
CoreEventType::MsgDelivered { chat_id, msg_id } => MsgDelivered {
|
||||
chat_id: chat_id.to_u32(),
|
||||
msg_id: msg_id.to_u32(),
|
||||
},
|
||||
CoreEventType::MsgFailed { chat_id, msg_id } => MsgFailed {
|
||||
chat_id: chat_id.to_u32(),
|
||||
msg_id: msg_id.to_u32(),
|
||||
},
|
||||
CoreEventType::MsgRead { chat_id, msg_id } => MsgRead {
|
||||
chat_id: chat_id.to_u32(),
|
||||
msg_id: msg_id.to_u32(),
|
||||
},
|
||||
CoreEventType::MsgDeleted { chat_id, msg_id } => MsgDeleted {
|
||||
chat_id: chat_id.to_u32(),
|
||||
msg_id: msg_id.to_u32(),
|
||||
},
|
||||
CoreEventType::ChatModified(chat_id) => ChatModified {
|
||||
chat_id: chat_id.to_u32(),
|
||||
},
|
||||
CoreEventType::ChatEphemeralTimerModified { chat_id, timer } => {
|
||||
ChatEphemeralTimerModified {
|
||||
chat_id: chat_id.to_u32(),
|
||||
timer: timer.to_u32(),
|
||||
}
|
||||
}
|
||||
CoreEventType::ChatDeleted { chat_id } => ChatDeleted {
|
||||
chat_id: chat_id.to_u32(),
|
||||
},
|
||||
CoreEventType::ContactsChanged(contact) => ContactsChanged {
|
||||
contact_id: contact.map(|c| c.to_u32()),
|
||||
},
|
||||
CoreEventType::LocationChanged(contact) => LocationChanged {
|
||||
contact_id: contact.map(|c| c.to_u32()),
|
||||
},
|
||||
CoreEventType::ConfigureProgress { progress, comment } => {
|
||||
ConfigureProgress { progress, comment }
|
||||
}
|
||||
CoreEventType::ImexProgress(progress) => ImexProgress { progress },
|
||||
CoreEventType::ImexFileWritten(path) => ImexFileWritten {
|
||||
path: path.to_str().unwrap_or_default().to_owned(),
|
||||
},
|
||||
CoreEventType::SecurejoinInviterProgress {
|
||||
contact_id,
|
||||
chat_type,
|
||||
chat_id,
|
||||
progress,
|
||||
} => SecurejoinInviterProgress {
|
||||
contact_id: contact_id.to_u32(),
|
||||
chat_type: chat_type.into(),
|
||||
chat_id: chat_id.to_u32(),
|
||||
progress,
|
||||
},
|
||||
CoreEventType::SecurejoinJoinerProgress {
|
||||
contact_id,
|
||||
progress,
|
||||
} => SecurejoinJoinerProgress {
|
||||
contact_id: contact_id.to_u32(),
|
||||
progress,
|
||||
},
|
||||
CoreEventType::ConnectivityChanged => ConnectivityChanged,
|
||||
CoreEventType::SelfavatarChanged => SelfavatarChanged,
|
||||
CoreEventType::ConfigSynced { key } => ConfigSynced {
|
||||
key: key.to_string(),
|
||||
},
|
||||
CoreEventType::WebxdcStatusUpdate {
|
||||
msg_id,
|
||||
status_update_serial,
|
||||
} => WebxdcStatusUpdate {
|
||||
msg_id: msg_id.to_u32(),
|
||||
status_update_serial: status_update_serial.to_u32(),
|
||||
},
|
||||
CoreEventType::WebxdcRealtimeData { msg_id, data } => WebxdcRealtimeData {
|
||||
msg_id: msg_id.to_u32(),
|
||||
data,
|
||||
},
|
||||
CoreEventType::WebxdcRealtimeAdvertisementReceived { msg_id } => {
|
||||
WebxdcRealtimeAdvertisementReceived {
|
||||
msg_id: msg_id.to_u32(),
|
||||
}
|
||||
}
|
||||
CoreEventType::WebxdcInstanceDeleted { msg_id } => WebxdcInstanceDeleted {
|
||||
msg_id: msg_id.to_u32(),
|
||||
},
|
||||
CoreEventType::AccountsBackgroundFetchDone => AccountsBackgroundFetchDone,
|
||||
CoreEventType::ChatlistItemChanged { chat_id } => ChatlistItemChanged {
|
||||
chat_id: chat_id.map(|id| id.to_u32()),
|
||||
},
|
||||
CoreEventType::ChatlistChanged => ChatlistChanged,
|
||||
CoreEventType::EventChannelOverflow { n } => EventChannelOverflow { n },
|
||||
CoreEventType::AccountsChanged => AccountsChanged,
|
||||
CoreEventType::AccountsItemChanged => AccountsItemChanged,
|
||||
CoreEventType::IncomingCall {
|
||||
msg_id,
|
||||
chat_id,
|
||||
place_call_info,
|
||||
has_video,
|
||||
} => IncomingCall {
|
||||
msg_id: msg_id.to_u32(),
|
||||
chat_id: chat_id.to_u32(),
|
||||
place_call_info,
|
||||
has_video,
|
||||
},
|
||||
CoreEventType::IncomingCallAccepted {
|
||||
msg_id,
|
||||
chat_id,
|
||||
from_this_device,
|
||||
} => IncomingCallAccepted {
|
||||
msg_id: msg_id.to_u32(),
|
||||
chat_id: chat_id.to_u32(),
|
||||
from_this_device,
|
||||
},
|
||||
CoreEventType::OutgoingCallAccepted {
|
||||
msg_id,
|
||||
chat_id,
|
||||
accept_call_info,
|
||||
} => OutgoingCallAccepted {
|
||||
msg_id: msg_id.to_u32(),
|
||||
chat_id: chat_id.to_u32(),
|
||||
accept_call_info,
|
||||
},
|
||||
CoreEventType::CallEnded { msg_id, chat_id } => CallEnded {
|
||||
msg_id: msg_id.to_u32(),
|
||||
chat_id: chat_id.to_u32(),
|
||||
},
|
||||
CoreEventType::TransportsModified => TransportsModified,
|
||||
|
||||
#[allow(unreachable_patterns)]
|
||||
#[cfg(test)]
|
||||
_ => unreachable!("This is just to silence a rust_analyzer false-positive"),
|
||||
}
|
||||
}
|
||||
}
|
||||