Compare commits

..

1 Commits

Author SHA1 Message Date
holger krekel
d13427cc84 fix bug that lead to all liveconfig tests failing 2019-08-13 22:03:24 +02:00
428 changed files with 35983 additions and 102100 deletions

196
.circleci/config.yml Normal file
View File

@@ -0,0 +1,196 @@
version: 2.1
executors:
default:
docker:
- image: filecoin/rust:latest
working_directory: /mnt/crate
restore-workspace: &restore-workspace
attach_workspace:
at: /mnt
restore-cache: &restore-cache
restore_cache:
keys:
- cargo-v0-{{ 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
- setup_remote_docker:
docker_layer_caching: true
# TODO: move into image
- run:
name: Install Docker client
command: |
set -x
VER="18.09.2"
curl -L -o /tmp/docker-$VER.tgz https://download.docker.com/linux/static/stable/x86_64/docker-$VER.tgz
tar -xz -C /tmp -f /tmp/docker-$VER.tgz
mv /tmp/docker/* /usr/bin
- run:
name: Test (<< parameters.target >>)
command: TARGET=<< parameters.target >> ci/run.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-v0-{{ 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: cargo update
- run: cargo fetch
- run: rustc +stable --version
- run: rustc +$(cat rust-toolchain) --version
- run: rm -rf .git
- persist_to_workspace:
root: /mnt
paths:
- crate
- save_cache:
key: cargo-v0-{{ 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/run.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_test_docs_wheel:
machine: True
steps:
- checkout
# - run: docker pull deltachat/doxygen
- run: docker pull deltachat/coredeps
- run:
name: build docs, run tests and build wheels
command: ci_scripts/ci_run.sh
environment:
TESTS: 1
DOCS: 1
- 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
upload_docs_wheels:
machine: True
steps:
- checkout
- attach_workspace:
at: workspace
- run: ls -laR workspace
- run: ci_scripts/ci_upload.sh workspace/py-docs workspace/wheelhouse
workflows:
version: 2.1
test:
jobs:
- build_test_docs_wheel
- upload_docs_wheels:
requires:
- build_test_docs_wheel
- cargo_fetch
- rustfmt:
requires:
- cargo_fetch
# 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

7
.gitattributes vendored
View File

@@ -2,16 +2,9 @@
# ensures this even if the user has not set core.autocrlf. # ensures this even if the user has not set core.autocrlf.
* text=auto * text=auto
# This directory contains email messages verbatim, and changing CRLF to
# LF will corrupt them.
test-data/* text=false
# binary files should be detected by git, however, to be sure, you can add them here explicitly # binary files should be detected by git, however, to be sure, you can add them here explicitly
*.png binary *.png binary
*.jpg binary *.jpg binary
*.gif binary *.gif binary
*.ico binary *.ico binary
*.py diff=python
*.rs diff=rust
*.md diff=markdown

View File

@@ -1,9 +0,0 @@
version: 2
updates:
- package-ecosystem: "cargo"
directory: "/"
schedule:
interval: "monthly"
commit-message:
prefix: "cargo"
open-pull-requests-limit: 50

26
.github/mergeable.yml vendored
View File

@@ -1,26 +0,0 @@
version: 2
mergeable:
- when: pull_request.*
name: "Changelog check"
validate:
- do: or
validate:
- do: description
must_include:
regex: '#skip-changelog'
- do: and
validate:
- do: dependent
changed:
file: 'src/**'
required: ['CHANGELOG.md']
- do: dependent
changed:
file: 'deltachat-ffi/**'
required: ['CHANGELOG.md']
fail:
- do: checks
status: 'action_required'
payload:
title: Changelog might need an update
summary: "Check if CHANGELOG.md needs an update or add #skip-changelog to the PR description."

View File

@@ -1,143 +0,0 @@
name: Rust CI
on:
pull_request:
push:
branches:
- master
- staging
- trying
env:
RUSTFLAGS: -Dwarnings
jobs:
fmt:
name: Rustfmt
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: stable
override: true
- run: rustup component add rustfmt
- name: Cache rust cargo artifacts
uses: swatinem/rust-cache@v1
- uses: actions-rs/cargo@v1
with:
command: fmt
args: --all -- --check
run_clippy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
with:
toolchain: stable
components: clippy
override: true
- name: Cache rust cargo artifacts
uses: swatinem/rust-cache@v1
- uses: actions-rs/clippy-check@v1
with:
token: ${{ secrets.GITHUB_TOKEN }}
args: --workspace --tests --examples --benches
docs:
name: Rust doc comments
runs-on: ubuntu-latest
env:
RUSTDOCFLAGS: -Dwarnings
steps:
- name: Checkout sources
uses: actions/checkout@v2
- name: Install rust stable toolchain
uses: actions-rs/toolchain@v1
with:
toolchain: stable
profile: minimal
components: rust-docs
override: true
- name: Cache rust cargo artifacts
uses: swatinem/rust-cache@v1
- name: Rustdoc
uses: actions-rs/cargo@v1
with:
command: doc
args: --document-private-items --no-deps
build_and_test:
name: Build and test
strategy:
matrix:
include:
# Currently used Rust version, same as in `rust-toolchain` file.
- os: ubuntu-latest
rust: 1.61.0
python: 3.9
- os: windows-latest
rust: 1.61.0
python: false # Python bindings compilation on Windows is not supported.
# Minimum Supported Rust Version = 1.56.0
#
# Minimum Supported Python Version = 3.7
# This is the minimum version for which manylinux Python wheels are
# built.
- os: ubuntu-latest
rust: 1.56.0
python: 3.7
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@master
- name: Install ${{ matrix.rust }}
uses: actions-rs/toolchain@v1
with:
toolchain: ${{ matrix.rust }}
override: true
- name: Cache rust cargo artifacts
uses: swatinem/rust-cache@v1
- name: check
uses: actions-rs/cargo@v1
with:
command: check
args: --all --bins --examples --tests --features repl --benches
- name: tests
uses: actions-rs/cargo@v1
with:
command: test
args: --all
- name: install python
if: ${{ matrix.python }}
uses: actions/setup-python@v2
with:
python-version: ${{ matrix.python }}
- name: install tox
if: ${{ matrix.python }}
run: pip install tox
- name: build C library
if: ${{ matrix.python }}
uses: actions-rs/cargo@v1
with:
command: build
args: -p deltachat_ffi
- name: run python tests
if: ${{ matrix.python }}
env:
DCC_NEW_TMP_EMAIL: ${{ secrets.DCC_NEW_TMP_EMAIL }}
DCC_RS_TARGET: debug
DCC_RS_DEV: ${{ github.workspace }}
working-directory: python
run: tox -e lint,mypy,doc,py3

View File

@@ -1,21 +0,0 @@
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@v1.1.1
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}}

View File

@@ -1,66 +0,0 @@
name: JSON-RPC API Test
on:
push:
branches: [master]
pull_request:
branches: [master]
env:
CARGO_TERM_COLOR: always
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: 1.56.0
override: true
- name: Rust Cache
uses: Swatinem/rust-cache@v1.3.0
- name: Build
run: cargo build --verbose --features webserver -p deltachat-jsonrpc
- name: Run tests
run: cargo test --verbose --features webserver -p deltachat-jsonrpc
ts_bindings:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Use Node.js 16.x
uses: actions/setup-node@v1
with:
node-version: 16.x
- uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: 1.56.0
override: true
- name: Rust Cache
uses: Swatinem/rust-cache@v1.3.0
- name: npm i
run: |
cd deltachat-jsonrpc/typescript
npm i
- name: npm run generate-bindings
run: |
cd deltachat-jsonrpc/typescript
npm run generate-bindings
- name: npm run check ts
run: |
cd deltachat-jsonrpc/typescript
npx tsc --noEmit
- name: run integration tests
run: |
cd deltachat-jsonrpc/typescript
npm run build
cargo build --features webserver
npm run test:integration
- name: run prettier
run: |
cd deltachat-jsonrpc/typescript
npm run prettier:check

View File

@@ -1,32 +0,0 @@
# documentation: https://github.com/deltachat/sysadmin/tree/master/download.delta.chat
name: Delete node PR previews
on:
pull_request:
types: [closed]
jobs:
delete:
runs-on: ubuntu-latest
steps:
- name: Get Pullrequest ID
id: getid
run: |
export PULLREQUEST_ID=$(jq .number < $GITHUB_EVENT_PATH)
echo ::set-output name=prid::$PULLREQUEST_ID
- name: Renaming
run: |
# create empty file to copy it over the outdated deliverable on download.delta.chat
echo "This preview build is outdated and has been removed." > empty
cp empty deltachat-node-${{ steps.getid.outputs.prid }}.tar.gz
- name: Replace builds with dummy files
uses: horochx/deploy-via-scp@v1.0.1
with:
user: ${{ secrets.USERNAME }}
key: ${{ secrets.SSH_KEY }}
host: "download.delta.chat"
port: 22
local: "deltachat-node-${{ steps.getid.outputs.prid }}.tar.gz"
remote: "/var/www/html/download/node/preview/"

View File

@@ -1,34 +0,0 @@
name: Generate & upload node.js documentation
on:
push:
branches:
- master
jobs:
generate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- name: Use Node.js 16.x
uses: actions/setup-node@v1
with:
node-version: 16.x
- name: npm install and generate documentation
run: |
cd node
npm i --ignore-scripts
npx typedoc
mv docs js
- name: Upload
uses: horochx/deploy-via-scp@v1.0.1
with:
user: ${{ secrets.USERNAME }}
key: ${{ secrets.KEY }}
host: "delta.chat"
port: 22
local: "node/js"
remote: "/var/www/html/"

View File

@@ -1,165 +0,0 @@
name: 'node.js build'
on:
pull_request:
push:
tags:
- '*'
- '!py-*'
jobs:
prebuild:
name: 'prebuild'
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-18.04, macos-latest, windows-latest]
steps:
- name: Checkout
uses: actions/checkout@v2
- uses: actions/setup-node@v2
with:
node-version: '16'
- name: System info
run: |
rustc -vV
rustup -vV
cargo -vV
npm --version
node --version
- name: Cache node modules
uses: actions/cache@v2
with:
path: |
${{ env.APPDATA }}/npm-cache
~/.npm
key: ${{ matrix.os }}-node-${{ hashFiles('**/package.json') }}
- name: Cache cargo index
uses: actions/cache@v2
with:
path: |
~/.cargo/registry/
~/.cargo/git
target
key: ${{ matrix.os }}-cargo-index-${{ hashFiles('**/Cargo.lock') }}-2
- name: Install dependencies & build
if: steps.cache.outputs.cache-hit != 'true'
run: |
cd node
npm install --verbose
- name: Build Prebuild
run: |
cd node
npm run prebuildify
tar -zcvf "${{ matrix.os }}.tar.gz" -C prebuilds .
- name: Upload Prebuild
uses: actions/upload-artifact@v1
with:
name: ${{ matrix.os }}
path: node/${{ matrix.os }}.tar.gz
pack-module:
needs: prebuild
name: 'Package deltachat-node and upload to download.delta.chat'
runs-on: ubuntu-18.04
steps:
- name: install tree
run: sudo apt install tree
- name: Checkout
uses: actions/checkout@v2
- uses: actions/setup-node@v2
with:
node-version: '16'
- name: get tag
id: tag
uses: dawidd6/action-get-tag@v1
continue-on-error: true
- name: Get Pullrequest ID
id: prepare
run: |
tag=${{ steps.tag.outputs.tag }}
if [ -z "$tag" ]; then
node -e "console.log('DELTACHAT_NODE_TAR_GZ=deltachat-node-' + '${{ github.ref }}'.split('/')[2] + '.tar.gz')" >> $GITHUB_ENV
else
echo "DELTACHAT_NODE_TAR_GZ=deltachat-node-${{ steps.tag.outputs.tag }}.tar.gz" >> $GITHUB_ENV
echo "No preview will be uploaded this time, but the $tag release"
fi
- name: System info
run: |
rustc -vV
rustup -vV
cargo -vV
npm --version
node --version
echo $DELTACHAT_NODE_TAR_GZ
- name: Download ubuntu prebuild
uses: actions/download-artifact@v1
with:
name: ubuntu-18.04
- name: Download macos prebuild
uses: actions/download-artifact@v1
with:
name: macos-latest
- name: Download windows prebuild
uses: actions/download-artifact@v1
with:
name: windows-latest
- shell: bash
run: |
mkdir node/prebuilds
tar -xvzf ubuntu-18.04/ubuntu-18.04.tar.gz -C node/prebuilds
tar -xvzf macos-latest/macos-latest.tar.gz -C node/prebuilds
tar -xvzf windows-latest/windows-latest.tar.gz -C node/prebuilds
tree node/prebuilds
rm -rf ubuntu-18.04 macos-latest windows-latest
- name: install dependencies without running scripts
run: |
npm install --ignore-scripts
- name: build constants
run: |
npm run build:core:constants
- name: build typescript part
run: |
npm run build:bindings:ts
- name: package
shell: bash
run: |
mv node/README.md README.md
npm pack .
ls -lah
mv $(find deltachat-node-*) $DELTACHAT_NODE_TAR_GZ
- name: Upload Prebuild
uses: actions/upload-artifact@v1
with:
name: deltachat-node.tgz
path: ${{ env.DELTACHAT_NODE_TAR_GZ }}
# Upload to download.delta.chat/node/preview/
- name: Upload deltachat-node preview to download.delta.chat/node/preview/
if: ${{ ! steps.tag.outputs.tag }}
id: upload-preview
shell: bash
run: |
echo -e "${{ secrets.SSH_KEY }}" >__TEMP_INPUT_KEY_FILE
chmod 600 __TEMP_INPUT_KEY_FILE
scp -o StrictHostKeyChecking=no -v -i __TEMP_INPUT_KEY_FILE -P "22" -r $DELTACHAT_NODE_TAR_GZ "${{ secrets.USERNAME }}"@"download.delta.chat":"/var/www/html/download/node/preview/"
continue-on-error: true
- name: "Post links to details"
if: steps.upload-preview.outcome == 'success'
run: node ./node/scripts/postLinksToDetails.js
env:
URL: preview/${{ env.DELTACHAT_NODE_TAR_GZ }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# Upload to download.delta.chat/node/
- name: Upload deltachat-node build to download.delta.chat/node/
if: ${{ steps.tag.outputs.tag }}
id: upload
shell: bash
run: |
echo -e "${{ secrets.SSH_KEY }}" >__TEMP_INPUT_KEY_FILE
chmod 600 __TEMP_INPUT_KEY_FILE
scp -o StrictHostKeyChecking=no -v -i __TEMP_INPUT_KEY_FILE -P "22" -r $DELTACHAT_NODE_TAR_GZ "${{ secrets.USERNAME }}"@"download.delta.chat":"/var/www/html/download/node/"

View File

@@ -1,67 +0,0 @@
name: 'node.js tests'
on:
pull_request:
push:
branches:
- master
- staging
- trying
jobs:
tests:
name: 'tests'
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-18.04, macos-latest, windows-latest]
steps:
- name: Checkout
uses: actions/checkout@v2
- uses: actions/setup-node@v2
with:
node-version: '16'
- name: System info
run: |
rustc -vV
rustup -vV
cargo -vV
npm --version
node --version
- name: Cache node modules
uses: actions/cache@v2
with:
path: |
${{ env.APPDATA }}/npm-cache
~/.npm
key: ${{ matrix.os }}-node-${{ hashFiles('**/package.json') }}
- name: Cache cargo index
uses: actions/cache@v2
with:
path: |
~/.cargo/registry/
~/.cargo/git
target
key: ${{ matrix.os }}-cargo-index-${{ hashFiles('**/Cargo.lock') }}-2
- name: Install dependencies & build
if: steps.cache.outputs.cache-hit != 'true'
run: |
cd node
npm install --verbose
- name: Test
if: runner.os != 'Windows'
run: |
cd node
npm run test
env:
DCC_NEW_TMP_EMAIL: ${{ secrets.DCC_NEW_TMP_EMAIL }}
- name: Run tests on Windows, except lint
if: runner.os == 'Windows'
run: |
cd node
npm run test:mocha
env:
DCC_NEW_TMP_EMAIL: ${{ secrets.DCC_NEW_TMP_EMAIL }}

View File

@@ -1,32 +0,0 @@
# Manually triggered action to build a Windows repl.exe which users can
# download to debug complex bugs.
name: Build Windows REPL .exe
on:
workflow_dispatch:
jobs:
build_repl:
name: Build REPL example
runs-on: windows-latest
steps:
- uses: actions/checkout@master
- name: Install Rust
uses: actions-rs/toolchain@v1
with:
toolchain: 1.50.0
override: true
- name: build
uses: actions-rs/cargo@v1
with:
command: build
args: --example repl --features repl,vendored
- name: Upload binary
uses: actions/upload-artifact@v2
with:
name: repl.exe
path: 'target/debug/examples/repl.exe'

View File

@@ -1,28 +0,0 @@
name: Build & Deploy Documentation on rs.delta.chat
on:
push:
branches:
- master
- docs-gh-action
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- uses: actions-rs/toolchain@v1
- name: Build the documentation with cargo
run: |
cargo doc --package deltachat --no-deps
- name: Upload to rs.delta.chat
uses: up9cloud/action-rsync@v1.3
env:
USER: ${{ secrets.USERNAME }}
KEY: ${{ secrets.KEY }}
HOST: "delta.chat"
SOURCE: "target/doc"
TARGET: "/var/www/html/rs/"

View File

@@ -1,28 +0,0 @@
name: Build & Deploy Documentation on cffi.delta.chat
on:
push:
branches:
- master
- docs-gh-action
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- uses: actions-rs/toolchain@v1
- name: Build the documentation with cargo
run: |
cargo doc --package deltachat_ffi --no-deps
- name: Upload to cffi.delta.chat
uses: up9cloud/action-rsync@v1.3
env:
USER: ${{ secrets.USERNAME }}
KEY: ${{ secrets.KEY }}
HOST: "delta.chat"
SOURCE: "target/doc"
TARGET: "/var/www/html/cffi/"

22
.gitignore vendored
View File

@@ -1,6 +1,5 @@
/target /target
**/*.rs.bk **/*.rs.bk
/build
# ignore vi temporaries # ignore vi temporaries
*~ *~
@@ -17,26 +16,5 @@ python/.tox
*.egg-info *.egg-info
__pycache__ __pycache__
python/src/deltachat/capi*.so python/src/deltachat/capi*.so
python/.venv/
python/liveconfig* python/liveconfig*
# ignore doxgen generated files
deltachat-ffi/html
deltachat-ffi/xml
.rsynclist
coverage/
.DS_Store
.vscode/launch.json
python/accounts.txt
python/all-testaccounts.txt
tmp/
# from deltachat-node
node_modules/
node/build/
node/dist/
node/prebuilds/
node/.nyc_output/

View File

@@ -1,42 +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

1
.npmrc
View File

@@ -1 +0,0 @@
package-lock=false

File diff suppressed because it is too large Load Diff

View File

@@ -1,50 +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()
add_custom_command(
OUTPUT
"target/release/libdeltachat.a"
"target/release/libdeltachat.${DYNAMIC_EXT}"
"target/release/pkgconfig/deltachat.pc"
COMMAND
PREFIX=${CMAKE_INSTALL_PREFIX}
LIBDIR=${CMAKE_INSTALL_FULL_LIBDIR}
INCLUDEDIR=${CMAKE_INSTALL_FULL_INCLUDEDIR}
${CARGO} build --release --no-default-features
# Build in `deltachat-ffi` directory instead of using
# `--package deltachat_ffi` to avoid feature resolver version
# "1" bug which makes `--no-default-features` affect only
# `deltachat`, but not `deltachat-ffi` package.
#
# We can't enable version "2" resolver [1] because it is not
# stable yet on rust 1.50.0.
#
# [1] https://doc.rust-lang.org/nightly/cargo/reference/features.html#resolver-version-2-command-line-flags
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/deltachat-ffi
)
add_custom_target(
lib_deltachat
ALL
DEPENDS
"target/release/libdeltachat.a"
"target/release/libdeltachat.${DYNAMIC_EXT}"
"target/release/pkgconfig/deltachat.pc"
)
install(FILES "deltachat-ffi/deltachat.h" DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})
install(FILES "target/release/libdeltachat.a" DESTINATION ${CMAKE_INSTALL_LIBDIR})
install(FILES "target/release/libdeltachat.${DYNAMIC_EXT}" DESTINATION ${CMAKE_INSTALL_LIBDIR})
install(FILES "target/release/pkgconfig/deltachat.pc" DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig)

5250
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,137 +1,74 @@
[package] [package]
name = "deltachat" name = "deltachat"
version = "1.86.0" version = "1.0.0-alpha.3"
authors = ["Delta Chat Developers (ML) <delta@codespeak.net>"] authors = ["dignifiedquire <dignifiedquire@gmail.com>"]
edition = "2021" edition = "2018"
license = "MPL-2.0" license = "MPL"
rust-version = "1.56"
[profile.dev] [build-dependencies]
debug = 0 cc = "1.0.35"
panic = 'abort' pkg-config = "0.3"
[profile.release]
lto = true
panic = 'abort'
[dependencies] [dependencies]
deltachat_derive = { path = "./deltachat_derive" } deltachat_derive = { path = "./deltachat_derive" }
libc = "0.2.51"
ansi_term = { version = "0.12.1", optional = true } pgp = { version = "0.2", default-features = false }
anyhow = "1" hex = "0.3.2"
async-imap = { git = "https://github.com/async-email/async-imap" } sha2 = "0.8.0"
async-native-tls = { version = "0.3" } rand = "0.6.5"
async-smtp = { git = "https://github.com/async-email/async-smtp", branch="master", default-features=false, features = ["smtp-transport", "socks5"] } smallvec = "0.6.9"
async-std-resolver = "0.21" reqwest = "0.9.15"
async-std = { version = "1" } num-derive = "0.2.5"
async-tar = { version = "0.4", default-features=false } num-traits = "0.2.6"
backtrace = "0.3" native-tls = "0.2.3"
base64 = "0.13" lettre = "0.9.0"
bitflags = "1.3" imap = "1.0.1"
chrono = { version = "0.4", default-features=false, features = ["clock", "std"] } mmime = "0.1.0"
dirs = { version = "4", optional=true } base64 = "0.10"
email = { git = "https://github.com/deltachat/rust-email", branch = "master" } charset = "0.1"
encoded-words = { git = "https://github.com/async-email/encoded-words", branch="master" }
escaper = "0.1"
futures = "0.3"
hex = "0.4.0"
image = { version = "0.24.1", default-features=false, features = ["gif", "jpeg", "ico", "png", "pnm", "webp", "bmp"] }
kamadak-exif = "0.5"
lettre_email = { git = "https://github.com/deltachat/lettre", branch = "master" }
libc = "0.2"
log = {version = "0.4.16", optional = true }
mailparse = "0.13"
native-tls = "0.2"
num_cpus = "1.13"
num-derive = "0.3"
num-traits = "0.2"
once_cell = "1.12.0"
percent-encoding = "2.0" percent-encoding = "2.0"
pgp = { version = "0.7", default-features = false }
pretty_env_logger = { version = "0.4", optional = true }
quick-xml = "0.23"
r2d2 = "0.8"
r2d2_sqlite = "0.20"
rand = "0.7"
regex = "1.5"
rusqlite = { version = "0.27", features = ["sqlcipher"] }
rust-hsluv = "0.1"
rustyline = { version = "9", optional = true }
sanitize-filename = "0.4"
serde_json = "1.0"
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
sha-1 = "0.10" serde_json = "1.0"
sha2 = "0.10" chrono = "0.4.6"
smallvec = "1" failure = "0.1.5"
strum = "0.24" failure_derive = "0.1.5"
strum_macros = "0.24" # TODO: make optional
surf = { version = "2.3", default-features = false, features = ["h1-client"] } rustyline = "4.1.0"
thiserror = "1" lazy_static = "1.3.0"
toml = "0.5" regex = "1.1.6"
url = "2" rusqlite = { version = "0.20", features = ["bundled"] }
uuid = { version = "1", features = ["serde", "v4"] } addr = "0.2.0"
fast-socks5 = "0.4" r2d2_sqlite = "0.12.0"
humansize = "1" r2d2 = "0.8.5"
qrcodegen = "1.7.0" strum = "0.15.0"
tagger = "4.3.3" strum_macros = "0.15.0"
textwrap = "0.15.0" thread-local-object = "0.1.0"
zip = { version = "0.6.2", default-features = false, features = ["deflate"] } backtrace = "0.3.33"
byteorder = "1.3.1"
itertools = "0.8.0"
[dev-dependencies] [dev-dependencies]
ansi_term = "0.12.0" tempfile = "3.0"
async-std = { version = "1", features = ["unstable", "attributes"] } pretty_assertions = "0.6.1"
criterion = { version = "0.3.4", features = ["async_std"] } pretty_env_logger = "0.3.0"
futures-lite = "1.12"
log = "0.4"
pretty_env_logger = "0.4"
proptest = { version = "1", default-features = false, features = ["std"] }
tempfile = "3"
[workspace] [workspace]
members = [ members = [
"deltachat-ffi", "deltachat-ffi",
"deltachat_derive", "deltachat_derive",
"deltachat-jsonrpc"
] ]
[[example]] [[example]]
name = "simple" name = "simple"
path = "examples/simple.rs" path = "examples/simple.rs"
required-features = ["repl"]
[[example]] [[example]]
name = "repl" name = "repl"
path = "examples/repl/main.rs" path = "examples/repl/main.rs"
required-features = ["repl"]
[[bench]]
name = "create_account"
harness = false
[[bench]]
name = "contacts"
harness = false
[[bench]]
name = "search_msgs"
harness = false
[[bench]]
name = "receive_emails"
harness = false
[[bench]]
name = "get_chat_msgs"
harness = false
[[bench]]
name = "get_chatlist"
harness = false
[features] [features]
default = ["vendored"] default = ["nightly", "ringbuf"]
internals = [] vendored = ["native-tls/vendored", "reqwest/default-tls-vendored"]
repl = ["internals", "rustyline", "log", "pretty_env_logger", "ansi_term", "dirs"]
vendored = ["async-native-tls/vendored", "async-smtp/native-tls-vendored", "rusqlite/bundled-sqlcipher-vendored-openssl"]
nightly = ["pgp/nightly"] nightly = ["pgp/nightly"]
ringbuf = ["pgp/ringbuf"]

View File

@@ -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 are (c) 2019 by Bjoern Petersen and contributors and released under the
Mozilla Public License Version 2.0, see below for a copy. 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 Mozilla Public License Version 2.0
================================== ==================================

View File

@@ -1,27 +1,26 @@
# Delta Chat Rust # Delta Chat Rust
> Deltachat-core written in Rust > Project porting deltachat-core to rust
[![Rust CI](https://github.com/deltachat/deltachat-core-rust/actions/workflows/ci.yml/badge.svg)](https://github.com/deltachat/deltachat-core-rust/actions/workflows/ci.yml) [![CircleCI build status][circle-shield]][circle] [![Appveyor build status][appveyor-shield]][appveyor]
Current commit on deltachat/deltachat-core: `12ef73c8e76185f9b78e844ea673025f56a959ab`.
## Installing Rust and Cargo ## 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: 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 ## Using the CLI client
Compile and run Delta Chat Core command line utility, using `cargo`: Compile and run Delta Chat Core using `cargo`:
``` ```
$ RUST_LOG=repl=info cargo run --example repl --features repl -- ~/deltachat-db cargo run --example repl -- /path/to/db
``` ```
where ~/deltachat-db is the database file. Delta Chat will create it if it does not exist.
Configure your account (if not already configured): Configure your account (if not already configured):
@@ -81,16 +80,6 @@ For more commands type:
> help > help
``` ```
## Installing libdeltachat system wide
```
$ git clone https://github.com/deltachat/deltachat-core-rust.git
$ cd deltachat-core-rust
$ cmake -B build . -DCMAKE_INSTALL_PREFIX=/usr
$ cmake --build build
$ sudo cmake --install build
```
## Development ## Development
```sh ```sh
@@ -100,13 +89,6 @@ $ cargo test --all
$ cargo build -p deltachat_ffi --release $ cargo build -p deltachat_ffi --release
``` ```
## Debugging environment variables
- `DCC_MIME_DEBUG`: if set outgoing and incoming message will be printed
- `RUST_LOG=repl=info,async_imap=trace,async_smtp=trace`: enable IMAP and
SMTP tracing in addition to info messages.
### Expensive tests ### Expensive tests
Some tests are expensive and marked with `#[ignore]`, to run these Some tests are expensive and marked with `#[ignore]`, to run these
@@ -119,24 +101,9 @@ $ cargo test -- --ignored
- `vendored`: When using Openssl for TLS, this bundles a vendored version. - `vendored`: When using Openssl for TLS, this bundles a vendored version.
- `nightly`: Enable nightly only performance and security related features. - `nightly`: Enable nightly only performance and security related features.
- `ringbuf`: Enable the use of [`slice_deque`](https://github.com/gnzlbg/slice_deque) in pgp.
## Language bindings and frontend projects [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/
Language bindings are available for: [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
- **C** \[[📂 source](./deltachat-ffi) | [📚 docs](https://c.delta.chat)\]
- **Node.js** \[[📂 source](./node) | [📦 npm](https://www.npmjs.com/package/deltachat-node) | [📚 docs](https://js.delta.chat)\]
- **Python** \[[📂 source](./python) | [📦 pypi](https://pypi.org/project/deltachat) | [📚 docs](https://py.delta.chat)\]
- **Go** \[[📂 source](https://github.com/deltachat/go-deltachat/)\]
- **Free Pascal** \[[📂 source](https://github.com/deltachat/deltachat-fp/)\]
- **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/)
- several **Bots**

20
appveyor.yml Normal file
View 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
cache:
- target
- C:\Users\appveyor\.cargo\registry

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

View File

@@ -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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

View File

@@ -1,83 +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"
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"
sodipodi:docname="icon-device.svg"
inkscape:version="1.0beta1 (32d4812, 2019-09-19)">
<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:window-maximized="0"
inkscape:current-layer="svg4344" />
<metadata
id="metadata4336">
Created by potrace 1.15, written by Peter Selinger 2001-2017
<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>
<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" />
<g
fill="#000000"
stroke="none"
style="fill:#ffffff;fill-opacity:1"
transform="matrix(0.00255113,0,0,-0.00255113,5.586152,38.200477)"
id="g4342">
<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"
id="path4338"
inkscape:connector-curvature="0" />
<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"
id="path4340"
inkscape:connector-curvature="0" />
</g>
</svg>

Before

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

View File

@@ -1,71 +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"
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"
sodipodi:docname="icon-saved-messages.svg"
inkscape:version="1.0beta1 (32d4812, 2019-09-19)">
<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:current-layer="svg4344"
inkscape:lockguides="false" />
<metadata
id="metadata4336">
Created by potrace 1.15, written by Peter Selinger 2001-2017
<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>
<rect
y="0"
x="0"
height="45"
width="45"
id="rect1420"
style="fill:#87aade;fill-opacity:1;stroke:none;stroke-width:0.968078" />
<path
id="rect846"
style="fill:#ffffff;stroke-width:0.58409804"
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" />
</svg>

Before

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.9 KiB

View File

@@ -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

View File

@@ -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" />

Binary file not shown.

Before

Width:  |  Height:  |  Size: 112 KiB

View File

@@ -1,40 +0,0 @@
use async_std::task::block_on;
use criterion::{black_box, criterion_group, criterion_main, Criterion};
use deltachat::contact::Contact;
use deltachat::context::Context;
use deltachat::Events;
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.into(), id, Events::new())
.await
.unwrap();
let book = (0..n)
.map(|i| format!("Name {}\naddr{}@example.org\n", i, i))
.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) {
c.bench_function("create 500 contacts", |b| {
b.iter(|| block_on(async { address_book_benchmark(black_box(500), black_box(0)).await }))
});
c.bench_function("create 100 contacts and read it 1000 times", |b| {
b.iter(|| block_on(async { address_book_benchmark(black_box(100), black_box(1000)).await }))
});
}
criterion_group!(benches, criterion_benchmark);
criterion_main!(benches);

View File

@@ -1,26 +0,0 @@
use async_std::path::PathBuf;
use async_std::task::block_on;
use criterion::{black_box, criterion_group, criterion_main, Criterion};
use deltachat::accounts::Accounts;
use tempfile::tempdir;
async fn create_accounts(n: u32) {
let dir = tempdir().unwrap();
let p: PathBuf = dir.path().join("accounts").into();
let mut accounts = Accounts::new(p.clone()).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| {
b.iter(|| block_on(async { create_accounts(black_box(1)).await }))
});
}
criterion_group!(benches, criterion_benchmark);
criterion_main!(benches);

View File

@@ -1,45 +0,0 @@
use async_std::path::Path;
use criterion::async_executor::AsyncStdExecutor;
use criterion::{black_box, criterion_group, criterion_main, Criterion};
use deltachat::chat::{self, ChatId};
use deltachat::chatlist::Chatlist;
use deltachat::context::Context;
use deltachat::Events;
async fn get_chat_msgs_benchmark(dbfile: &Path, chats: &[ChatId]) {
let id = 100;
let context = Context::new(dbfile.into(), id, Events::new())
.await
.unwrap();
for c in chats.iter().take(10) {
black_box(chat::get_chat_msgs(&context, *c, 0).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 chats: Vec<_> = async_std::task::block_on(async {
let context = Context::new((&path).into(), 100, Events::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(AsyncStdExecutor)
.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);

View File

@@ -1,29 +0,0 @@
use criterion::async_executor::AsyncStdExecutor;
use criterion::{black_box, criterion_group, criterion_main, Criterion};
use deltachat::chatlist::Chatlist;
use deltachat::context::Context;
use deltachat::Events;
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 context = async_std::task::block_on(async {
Context::new(path.into(), 100, Events::new()).await.unwrap()
});
c.bench_function("chatlist:try_load (Get Chatlist)", |b| {
b.to_async(AsyncStdExecutor)
.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);

View File

@@ -1,87 +0,0 @@
use async_std::{path::PathBuf, task::block_on};
use criterion::{
async_executor::AsyncStdExecutor, black_box, criterion_group, criterion_main, BatchSize,
Criterion,
};
use deltachat::{
config::Config,
context::Context,
dc_receive_imf::dc_receive_imf,
imex::{imex, ImexMode},
Events,
};
use tempfile::tempdir;
async fn recv_all_emails(context: Context) -> Context {
for i in 0..100 {
let imf_raw = format!(
"Subject: Benchmark
Message-ID: Mr.OssSYnOFkhR.{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.OssSYnOFkhR.{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,
);
dc_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.into(), id, Events::new())
.await
.unwrap();
let backup: PathBuf = std::env::current_dir()
.unwrap()
.join("delta-chat-backup.tar")
.into();
if backup.exists().await {
println!("Importing backup");
imex(&context, ImexMode::ImportBackup, &backup, 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| {
b.to_async(AsyncStdExecutor).iter_batched(
|| block_on(create_context()),
|context| recv_all_emails(black_box(context)),
BatchSize::LargeInput,
);
});
group.finish();
}
criterion_group!(benches, criterion_benchmark);
criterion_main!(benches);

View File

@@ -1,32 +0,0 @@
use async_std::task::block_on;
use criterion::{black_box, criterion_group, criterion_main, Criterion};
use deltachat::context::Context;
use deltachat::Events;
use std::path::Path;
async fn search_benchmark(path: impl AsRef<Path>) {
let dbfile = path.as_ref();
let id = 100;
let context = Context::new(dbfile.into(), id, Events::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") {
c.bench_function("search hello", |b| {
b.iter(|| block_on(async { search_benchmark(black_box(&path)).await }))
});
} else {
println!("env var not set: DELTACHAT_BENCHMARK_DATABASE");
}
}
criterion_group!(benches, criterion_benchmark);
criterion_main!(benches);

38
build.rs Normal file
View File

@@ -0,0 +1,38 @@
extern crate cc;
fn link_static(lib: &str) {
println!("cargo:rustc-link-lib=static={}", lib);
}
fn link_framework(fw: &str) {
println!("cargo:rustc-link-lib=framework={}", fw);
}
fn add_search_path(p: &str) {
println!("cargo:rustc-link-search={}", p);
}
fn build_tools() {
let mut config = cc::Build::new();
config.file("misc.c").compile("libtools.a");
println!("rerun-if-changed=build.rs");
println!("rerun-if-changed=misc.h");
println!("rerun-if-changed=misc.c");
}
fn main() {
build_tools();
add_search_path("/usr/local/lib");
let target = std::env::var("TARGET").unwrap();
if target.contains("-apple") || target.contains("-darwin") {
link_framework("CoreFoundation");
link_framework("CoreServices");
link_framework("Security");
}
// local tools
link_static("tools");
}

View File

@@ -1,9 +1,8 @@
#!/usr/bin/env bash #!/usr/bin/env bash
set -ex set -ex
shopt -s huponexit
#export RUST_TEST_THREADS=1 export RUST_TEST_THREADS=1
export RUST_BACKTRACE=1 export RUST_BACKTRACE=1
export RUSTFLAGS='--deny warnings' export RUSTFLAGS='--deny warnings'
export OPT="--target=$TARGET" export OPT="--target=$TARGET"
@@ -32,15 +31,16 @@ fi
if [[ $NORUN == "1" ]]; then if [[ $NORUN == "1" ]]; then
export CARGO_SUBCMD="build" export CARGO_SUBCMD="build"
else else
export CARGO_SUBCMD="test --all" export CARGO_SUBCMD="test"
export OPT="${OPT} " export OPT="${OPT} "
export OPT_RELEASE="${OPT_RELEASE} " export OPT_RELEASE="${OPT_RELEASE} "
export OPT_RELEASE_IGNORED="${OPT_RELEASE} -- --ignored" export OPT_RELEASE_IGNORED="${OPT_RELEASE} -- --ignored"
fi fi
# Run all the test configurations # Run all the test configurations:
# RUSTC_WRAPPER=SCCACHE seems to destroy parallelism / prolong the test
unset RUSTC_WRAPPER
$CARGO_CMD $CARGO_SUBCMD $OPT $CARGO_CMD $CARGO_SUBCMD $OPT
$CARGO_CMD $CARGO_SUBCMD $OPT_RELEASE $CARGO_CMD $CARGO_SUBCMD $OPT_RELEASE
$CARGO_CMD $CARGO_SUBCMD $OPT_RELEASE_IGNORED $CARGO_CMD $CARGO_SUBCMD $OPT_RELEASE_IGNORED
# Build the ffi lib
$CARGO_CMD $CARGO_SUBCMD $OPT_FFI_RELEASE

52
ci_scripts/README.md Normal file
View File

@@ -0,0 +1,52 @@
# Continuous Integration Scripts for Delta Chat
Continuous Integration is run through CircleCI
but is largely independent of it.
## Generating docker containers for performing build step work
All tests, docs and wheel building is run in docker containers:
- **coredeps/Dockerfile** specifies an image that contains all
of Delta Chat's core dependencies as linkable libraries.
It also serves to run python tests and build wheels
(binary packages for Python).
- **doxygen/Dockerfile** specifies an image that contains
the doxygen tool which is used to generate C-docs.
To run tests locally you can pull existing images from "docker.io",
the hub for sharing Docker images::
docker pull deltachat/coredeps
docker pull deltachat/doxygen
or 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
## ci_run.sh (main entrypoint called by circle-ci)
Once you have the docker images available
you can run python testing, documentation generation
and building binary wheels::
sh DOCS=1 TESTS=1 ci_scripts/ci_run.sh
## ci_upload.sh (uploading artifacts on success)
- python docs to `https://py.delta.chat/_unofficial_unreleased_docs/<BRANCH>`
- doxygen docs to `https://c.delta.chat/_unofficial_unreleased_docs/<BRANCH>`
- python wheels to `https://m.devpi.net/dc/<BRANCH>`
so that you install fully self-contained wheels like this:
`pip install -U -i https://m.devpi.net/dc/<BRANCH> deltachat`

22
ci_scripts/ci_run.sh Executable file
View File

@@ -0,0 +1,22 @@
# perform CI jobs on PRs and after merges to master.
# triggered from .circleci/config.yml
set -e -x
export BRANCH=${CIRCLE_BRANCH:-test7}
# run doxygen on c-source (needed by later doc-generation steps).
# XXX modifies the host filesystem docs/xml and docs/html directories
# XXX which you can then only remove with sudo as they belong to root
# XXX we don't do doxygen doc generation with Rust anymore, needs to be
# substituted with rust-docs
#if [ -n "$DOCS" ] ; then
# docker run --rm -it -v $PWD:/mnt -w /mnt/docs deltachat/doxygen doxygen
#fi
# run everything else inside docker (TESTS, DOCS, WHEELS)
docker run -e DCC_PY_LIVECONFIG -e BRANCH -e TESTS -e DOCS \
--rm -it -v $(pwd):/mnt -w /mnt \
deltachat/coredeps ci_scripts/run_all.sh

50
ci_scripts/ci_upload.sh Executable file
View File

@@ -0,0 +1,50 @@
#!/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
#DOXYDOCDIR=${1:?directory where doxygen docs to be found}
PYDOCDIR=${1:?directory with python docs}
WHEELHOUSEDIR=${2:?directory with pre-built wheels}
export BRANCH=${CIRCLE_BRANCH:?specify branch for uploading purposes}
# 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
#rsync -avz \
# -e "ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null" \
# "$DOXYDOCDIR/html/" \
# delta@py.delta.chat:build-c/${BRANCH}
echo -----------------------
echo upload wheels
echo -----------------------
# Bundle external shared libraries into the wheels
pushd $WHEELHOUSEDIR
pip 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

View File

@@ -0,0 +1,25 @@
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
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
# 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

View 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

View File

@@ -0,0 +1,12 @@
#!/bin/bash
PERL_VERSION=5.28.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

View 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

View File

@@ -0,0 +1,11 @@
#!/bin/bash
set -e -x
# Install Rust
curl https://sh.rustup.rs -sSf | sh -s -- --default-toolchain nightly-2019-07-10 -y
export PATH=/root/.cargo/bin:$PATH
rustc --version
# remove some 300-400 MB that we don't need for automated builds
rm -rf /root/.rustup/toolchains/nightly-2019-07-10-x86_64-unknown-linux-gnu/share/

View 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

54
ci_scripts/run_all.sh Executable file
View File

@@ -0,0 +1,54 @@
#!/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
# XXX we don't run liveconfig tests because they hang sometimes
# see https://github.com/deltachat/deltachat-core-rust/issues/331
unset DCC_PY_LIVECONFIG
tox --workdir "$TOXWORKDIR" -e lint,py27,py35,py36,py37,auditwheels
popd
fi
if [ -n "$DOCS" ]; then
echo -----------------------
echo generating python docs
echo -----------------------
(cd python && tox --workdir "$TOXWORKDIR" -e doc)
fi

View File

@@ -1,80 +0,0 @@
#!/usr/bin/env python3
# Examples:
#
# Original server that doesn't use SSL:
# ./proxy.py 8080 imap.nauta.cu 143
# ./proxy.py 8081 smtp.nauta.cu 25
#
# Original server that uses SSL:
# ./proxy.py 8080 testrun.org 993 --ssl
# ./proxy.py 8081 testrun.org 465 --ssl
from datetime import datetime
import argparse
import selectors
import ssl
import socket
import socketserver
class Proxy(socketserver.ThreadingTCPServer):
allow_reuse_address = True
def __init__(self, proxy_host, proxy_port, real_host, real_port, use_ssl):
self.real_host = real_host
self.real_port = real_port
self.use_ssl = use_ssl
super().__init__((proxy_host, proxy_port), RequestHandler)
class RequestHandler(socketserver.BaseRequestHandler):
def handle(self):
print('{} - {} CONNECTED.'.format(datetime.now(), self.client_address))
total = 0
real_server = (self.server.real_host, self.server.real_port)
with socket.create_connection(real_server) as sock:
if self.server.use_ssl:
context = ssl.create_default_context()
sock = context.wrap_socket(
sock, server_hostname=real_server[0])
forward = {self.request: sock, sock: self.request}
sel = selectors.DefaultSelector()
sel.register(self.request, selectors.EVENT_READ,
self.client_address)
sel.register(sock, selectors.EVENT_READ, real_server)
active = True
while active:
events = sel.select()
for key, mask in events:
print('\n{} - {} wrote:'.format(datetime.now(), key.data))
data = key.fileobj.recv(1024)
received = len(data)
total += received
print(data)
print('{} Bytes\nTotal: {} Bytes'.format(received, total))
if data:
forward[key.fileobj].sendall(data)
else:
print('\nCLOSING CONNECTION.\n\n')
forward[key.fileobj].close()
key.fileobj.close()
active = False
if __name__ == '__main__':
p = argparse.ArgumentParser(description='Simple Python Proxy')
p.add_argument(
"proxy_port", help="the port where the proxy will listen", type=int)
p.add_argument('host', help="the real host")
p.add_argument('port', help="the port of the real host", type=int)
p.add_argument("--ssl", help="use ssl to connect to the real host",
action="store_true")
args = p.parse_args()
with Proxy('', args.proxy_port, args.host, args.port, args.ssl) as proxy:
proxy.serve_forever()

View File

@@ -1,11 +1,11 @@
[package] [package]
name = "deltachat_ffi" name = "deltachat_ffi"
version = "1.86.0" version = "1.0.0-alpha.3"
description = "Deltachat FFI" description = "Deltachat FFI"
authors = ["Delta Chat Developers (ML) <delta@codespeak.net>"] authors = ["dignifiedquire <dignifiedquire@gmail.com>"]
edition = "2018" edition = "2018"
readme = "README.md" readme = "README.md"
license = "MPL-2.0" license = "MIT OR Apache-2.0"
keywords = ["deltachat", "chat", "openpgp", "email", "encryption"] keywords = ["deltachat", "chat", "openpgp", "email", "encryption"]
categories = ["cryptography", "std", "email"] categories = ["cryptography", "std", "email"]
@@ -17,16 +17,12 @@ crate-type = ["cdylib", "staticlib"]
[dependencies] [dependencies]
deltachat = { path = "../", default-features = false } deltachat = { path = "../", default-features = false }
libc = "0.2" libc = "0.2"
human-panic = "1" human-panic = "1.0.1"
num-traits = "0.2" num-traits = "0.2.6"
serde_json = "1.0"
async-std = "1"
anyhow = "1"
thiserror = "1"
rand = "0.7"
[features] [features]
default = ["vendored"] default = ["vendored", "nightly", "ringbuf"]
vendored = ["deltachat/vendored"] vendored = ["deltachat/vendored"]
nightly = ["deltachat/nightly"] nightly = ["deltachat/nightly"]
ringbuf = ["deltachat/ringbuf"]

File diff suppressed because it is too large Load Diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

View File

@@ -1,19 +0,0 @@
/* the code snippet frame, defaults to white which tends to get badly readable in combination with explaining text around */
div.fragment {
background-color: #e0e0e0;
border: 0;
padding: 1em;
border-radius: 6px;
}
code {
background-color: #e0e0e0;
padding-left: .5em;
padding-right: .5em;
border-radius: 6px;
}
li {
margin-bottom: .5em;
}

View File

@@ -1,38 +0,0 @@
<doxygenlayout version="1.0">
<!-- Generated by doxygen 1.8.20 -->
<!-- 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="modules" visible="yes" title="Constants" intro="Here is a list of constants:"/>
<tab type="pages" visible="yes" title="" intro=""/>
<tab type="namespaces" visible="yes" title="">
<tab type="namespacelist" visible="yes" title="" intro=""/>
<tab type="namespacemembers" visible="yes" title="" intro=""/>
</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>
</doxygenlayout>

View File

@@ -1,14 +1 @@
# Delta Chat C Interface # Delta Chat C Interface
## Installation
see `Installing libdeltachat system wide` in [../README.md](../README.md)
## Documentation
To generate the C Interface documentation,
call doxygen in the `deltachat-ffi` directory
and browse the `html` subdirectory.
If thinks work,
the documentation is also available online at <https://c.delta.chat>

View File

@@ -19,17 +19,15 @@ fn main() {
include_str!("deltachat.pc.in"), include_str!("deltachat.pc.in"),
name = "deltachat", name = "deltachat",
description = env::var("CARGO_PKG_DESCRIPTION").unwrap(), 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(), version = env::var("CARGO_PKG_VERSION").unwrap(),
libs_priv = libs_priv, libs_priv = libs_priv,
prefix = env::var("PREFIX").unwrap_or_else(|_| "/usr/local".to_string()), prefix = env::var("PREFIX").unwrap_or("/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()),
); );
fs::create_dir_all(target_path.join("pkgconfig")).unwrap(); fs::create_dir_all(target_path.join("pkgconfig")).unwrap();
fs::File::create(target_path.join("pkgconfig").join("deltachat.pc")) fs::File::create(target_path.join("pkgconfig").join("deltachat.pc"))
.unwrap() .unwrap()
.write_all(pkg_config.as_bytes()) .write_all(&pkg_config.as_bytes())
.unwrap(); .unwrap();
} }

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
prefix={prefix} prefix={prefix}
libdir={libdir} libdir=${{prefix}}/lib
includedir={includedir} includedir=${{prefix}}/include
Name: {name} Name: {name}
Description: {description} Description: {description}

View File

@@ -1,127 +0,0 @@
use crate::chat::ChatItem;
use crate::constants::DC_MSG_ID_DAYMARKER;
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>),
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::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::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::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::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<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);
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,245 +0,0 @@
//! # Legacy generic return values for C API.
use crate::message::MessageState;
use crate::qr::Qr;
use crate::summary::{Summary, SummaryPrefix};
use anyhow::Error;
use std::borrow::Cow;
/// 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.
#[derive(Debug)]
pub enum Lot {
Summary(Summary),
Qr(Qr),
Error(String),
}
#[repr(u8)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Meaning {
None = 0,
Text1Draft = 1,
Text1Username = 2,
Text1Self = 3,
}
impl Default for Meaning {
fn default() -> Self {
Meaning::None
}
}
impl Lot {
pub fn get_text1(&self) -> Option<&str> {
match self {
Self::Summary(summary) => match &summary.prefix {
None => None,
Some(SummaryPrefix::Draft(text)) => Some(text),
Some(SummaryPrefix::Username(username)) => Some(username),
Some(SummaryPrefix::Me(text)) => Some(text),
},
Self::Qr(qr) => match qr {
Qr::AskVerifyContact { .. } => None,
Qr::AskVerifyGroup { grpname, .. } => Some(grpname),
Qr::FprOk { .. } => None,
Qr::FprMismatch { .. } => None,
Qr::FprWithoutAddr { fingerprint, .. } => Some(fingerprint),
Qr::Account { domain } => Some(domain),
Qr::WebrtcInstance { domain, .. } => Some(domain),
Qr::Addr { .. } => None,
Qr::Url { url } => Some(url),
Qr::Text { text } => Some(text),
Qr::WithdrawVerifyContact { .. } => None,
Qr::WithdrawVerifyGroup { grpname, .. } => Some(grpname),
Qr::ReviveVerifyContact { .. } => None,
Qr::ReviveVerifyGroup { grpname, .. } => Some(grpname),
},
Self::Error(err) => Some(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) => 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::FprOk { .. } => LotState::QrFprOk,
Qr::FprMismatch { .. } => LotState::QrFprMismatch,
Qr::FprWithoutAddr { .. } => LotState::QrFprWithoutAddr,
Qr::Account { .. } => LotState::QrAccount,
Qr::WebrtcInstance { .. } => LotState::QrWebrtcInstance,
Qr::Addr { .. } => LotState::QrAddr,
Qr::Url { .. } => LotState::QrUrl,
Qr::Text { .. } => LotState::QrText,
Qr::WithdrawVerifyContact { .. } => LotState::QrWithdrawVerifyContact,
Qr::WithdrawVerifyGroup { .. } => LotState::QrWithdrawVerifyGroup,
Qr::ReviveVerifyContact { .. } => LotState::QrReviveVerifyContact,
Qr::ReviveVerifyGroup { .. } => LotState::QrReviveVerifyGroup,
},
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::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::WebrtcInstance { .. } => 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 { .. } => Default::default(),
Qr::ReviveVerifyContact { contact_id, .. } => contact_id.to_u32(),
Qr::ReviveVerifyGroup { .. } => 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, Clone, Copy, PartialEq, Eq)]
pub enum LotState {
// Default
Undefined = 0,
// Qr States
/// id=contact
QrAskVerifyContact = 200,
/// text1=groupname
QrAskVerifyGroup = 202,
/// id=contact
QrFprOk = 210,
/// id=contact
QrFprMismatch = 220,
/// text1=formatted fingerprint
QrFprWithoutAddr = 230,
/// text1=domain
QrAccount = 250,
/// text1=domain, text2=instance pattern
QrWebrtcInstance = 260,
/// id=contact
QrAddr = 320,
/// text1=text
QrText = 330,
/// text1=URL
QrUrl = 332,
/// text1=error string
QrError = 400,
QrWithdrawVerifyContact = 500,
/// text1=groupname
QrWithdrawVerifyGroup = 502,
QrReviveVerifyContact = 510,
/// text1=groupname
QrReviveVerifyGroup = 512,
// Message States
MsgInFresh = 10,
MsgInNoticed = 13,
MsgInSeen = 16,
MsgOutPreparing = 18,
MsgOutDraft = 19,
MsgOutPending = 20,
MsgOutFailed = 24,
MsgOutDelivered = 26,
MsgOutMdnRcvd = 28,
}
impl Default for LotState {
fn default() -> Self {
LotState::Undefined
}
}
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())
}
}

View File

@@ -1,421 +0,0 @@
use std::ffi::{CStr, CString};
use std::ptr;
/// Duplicates a string
///
/// returns an empty string if NULL is given, never returns NULL (exits on errors)
///
/// # Examples
///
/// ```rust,norun
/// use crate::string::{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);
/// assert_eq!(to_string_lossy(str_a_copy), "foobar");
/// 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)
} else {
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 {
/// The string contains an interior null byte
#[error("String contains an interior null byte")]
InteriorNullByte,
/// The string is not valid Unicode
#[error("String is not valid unicode")]
NotUnicode,
}
/// Extra convenience methods on [std::ffi::OsStr] to work with `*libc::c_char`.
///
/// The primary function of this trait is to more easily convert
/// [OsStr], [OsString] or [Path] into pointers to C strings. This always
/// allocates a new string since it is very common for the source
/// string not to have the required terminal null byte.
///
/// It is implemented for `AsRef<std::ffi::OsStr>>` trait, which
/// allows any type which implements this trait to transparently use
/// this. This is how the conversion for [Path] works.
///
/// [OsStr]: std::ffi::OsStr
/// [OsString]: std::ffi::OsString
/// [Path]: std::path::Path
///
/// # Example
///
/// ```
/// 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 {
/// Convert a [std::ffi::OsStr] to an [std::ffi::CString]
///
/// This is useful to convert e.g. a [std::path::Path] to
/// [*libc::c_char] by using
/// [Path::as_os_str()](std::path::Path::as_os_str) and
/// [CStr::as_ptr()](std::ffi::CStr::as_ptr).
///
/// This returns [CString] and not [&CStr] because not all [OsStr]
/// slices end with a null byte, particularly those coming from
/// [Path] do not have a null byte and having to handle this as
/// the caller would defeat the point of this function.
///
/// On Windows this requires that the [OsStr] contains valid
/// unicode, which should normally be the case for a [Path].
///
/// [CString]: std::ffi::CString
/// [CStr]: std::ffi::CStr
/// [OsStr]: std::ffi::OsStr
/// [Path]: std::path::Path
///
/// # Errors
///
/// Since a C `*char` is terminated by a NULL byte this conversion
/// will fail, when the [OsStr] has an interior null byte. The
/// function will return
/// `[Err]([CStringError::InteriorNullByte])`. When converting
/// from a [Path] it should be safe to
/// [`.unwrap()`](std::result::Result::unwrap) this anyway since a
/// [Path] should not contain interior null bytes.
///
/// On windows when the string contains invalid Unicode
/// `[Err]([CStringError::NotUnicode])` is returned.
fn to_c_string(&self) -> Result<CString, CStringError>;
}
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
})
}
#[cfg(target_os = "windows")]
fn to_c_string(&self) -> Result<CString, CStringError> {
os_str_to_c_string_unicode(&self)
}
}
// Implementation for os_str_to_c_string on windows.
#[allow(dead_code)]
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
}),
None => Err(CStringError::NotUnicode),
}
}
/// Convenience methods/associated functions for working with [CString]
trait CStringExt {
/// Create a new [CString], best effort
///
/// 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()
}
}
impl CStringExt for CString {}
/// Convenience methods to turn strings into C strings.
///
/// 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 {
/// Allocate a new raw C `*char` version of this string.
///
/// This allocates a new raw C string which must be freed using
/// `free`. It takes care of some common pitfalls with using
/// [CString.as_ptr].
///
/// [CString.as_ptr]: std::ffi::CString.as_ptr
///
/// # Panics
///
/// This function will panic when the original string contains an
/// interior null byte as this can not be represented in raw C
/// strings.
unsafe fn strdup(&self) -> *mut libc::c_char;
}
impl Strdup for str {
unsafe fn strdup(&self) -> *mut libc::c_char {
let tmp = CString::new_lossy(self);
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 {
if s.is_null() {
return "".into();
}
let cstr = unsafe { CStr::from_ptr(s) };
cstr.to_string_lossy().to_string()
}
pub(crate) fn to_opt_string_lossy(s: *const libc::c_char) -> Option<String> {
if s.is_null() {
return None;
}
Some(to_string_lossy(s))
}
/// Convert a C `*char` pointer to a [std::path::Path] slice.
///
/// This converts a `*libc::c_char` pointer to a [Path] slice. This
/// essentially has to convert the pointer to [std::ffi::OsStr] to do
/// so and thus is the inverse of [OsStrExt::to_c_string]. Just like
/// [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
/// 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 {
assert!(!s.is_null(), "cannot be used on null pointers");
use std::os::unix::ffi::OsStrExt;
unsafe {
let c_str = std::ffi::CStr::from_ptr(s).to_bytes();
let os_str = std::ffi::OsStr::from_bytes(c_str);
std::path::Path::new(os_str)
}
}
// 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 {
as_path_unicode(s)
}
// Implementation for as_path() on Windows.
//
// Having this as a separate function means it can be tested on unix
// too.
#[allow(dead_code)]
fn as_path_unicode<'a>(s: *const libc::c_char) -> &'a std::path::Path {
assert!(!s.is_null(), "cannot be used on null pointers");
let cstr = unsafe { CStr::from_ptr(s) };
let str = cstr.to_str().unwrap_or_else(|err| panic!("{}", err));
std::path::Path::new(str)
}
#[cfg(test)]
mod tests {
use super::*;
use libc::{free, strcmp};
#[test]
fn test_os_str_to_c_string_cwd() {
let some_dir = std::env::current_dir().unwrap();
some_dir.as_os_str().to_c_string().unwrap();
}
#[test]
fn test_os_str_to_c_string_unicode() {
let some_str = String::from("/some/valid/utf8");
let some_dir = std::path::Path::new(&some_str);
assert_eq!(
some_dir.as_os_str().to_c_string().unwrap(),
CString::new("/some/valid/utf8").unwrap()
);
}
#[test]
fn test_os_str_to_c_string_nul() {
let some_str = std::ffi::OsString::from("foo\x00bar");
assert_eq!(
some_str.to_c_string().err().unwrap(),
CStringError::InteriorNullByte
)
}
#[test]
fn test_path_to_c_string_cwd() {
let some_dir = std::env::current_dir().unwrap();
some_dir.to_c_string().unwrap();
}
#[test]
fn test_path_to_c_string_unicode() {
let some_str = String::from("/some/valid/utf8");
let some_dir = std::path::Path::new(&some_str);
assert_eq!(
some_dir.as_os_str().to_c_string().unwrap(),
CString::new("/some/valid/utf8").unwrap()
);
}
#[test]
fn test_os_str_to_c_string_unicode_fn() {
let some_str = std::ffi::OsString::from("foo");
assert_eq!(
os_str_to_c_string_unicode(&some_str).unwrap(),
CString::new("foo").unwrap()
);
}
#[test]
fn test_path_to_c_string_unicode_fn() {
let some_str = String::from("/some/path");
let some_path = std::path::Path::new(&some_str);
assert_eq!(
os_str_to_c_string_unicode(&some_path).unwrap(),
CString::new("/some/path").unwrap()
);
}
#[test]
fn test_os_str_to_c_string_unicode_fn_nul() {
let some_str = std::ffi::OsString::from("fooz\x00bar");
assert_eq!(
os_str_to_c_string_unicode(&some_str).err().unwrap(),
CStringError::InteriorNullByte
);
}
#[test]
fn test_as_path() {
let some_path = CString::new("/some/path").unwrap();
let ptr = some_path.as_ptr();
assert_eq!(as_path(ptr), std::ffi::OsString::from("/some/path"))
}
#[test]
fn test_as_path_unicode_fn() {
let some_path = CString::new("/some/path").unwrap();
let ptr = some_path.as_ptr();
assert_eq!(as_path_unicode(ptr), std::ffi::OsString::from("/some/path"));
}
#[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);
}
#[test]
fn test_strdup_str() {
unsafe {
let s = "hello".strdup();
let cmp = strcmp(s, b"hello\x00" as *const u8 as *const libc::c_char);
free(s as *mut libc::c_void);
assert_eq!(cmp, 0);
}
}
#[test]
fn test_strdup_string() {
unsafe {
let s = String::from("hello").strdup();
let cmp = strcmp(s, b"hello\x00" as *const u8 as *const libc::c_char);
free(s as *mut libc::c_void);
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());
}
}
}

View File

@@ -1,5 +0,0 @@
accounts/
types.ts
.cargo

View File

@@ -1,39 +0,0 @@
[package]
name = "deltachat-jsonrpc"
version = "0.1.0"
authors = ["Delta Chat Developers (ML) <delta@codespeak.net>"]
edition = "2021"
default-run = "webserver"
license = "MPL-2.0"
[[bin]]
name = "webserver"
path = "src/webserver.rs"
required-features = ["webserver"]
[dependencies]
anyhow = "1"
async-std = { version = "1", features = ["attributes"] }
deltachat = { path = ".." }
num-traits = "0.2"
serde = { version = "1.0", features = ["derive"] }
tempfile = "3.3.0"
log = "0.4"
async-channel = { version = "1.6.1" }
futures = { version = "0.3.19" }
serde_json = "1.0.75"
yerpc = { git = "https://github.com/Frando/yerpc", features = ["anyhow"] }
typescript-type-def = { git = "https://github.com/Frando/rust-typescript-type-def", branch = "yerpc", features = ["json_value"] }
# optional, depended on features
env_logger = { version = "0.9.0", optional = true }
tide = { version = "0.16.0", optional = true }
tide-websockets = { version = "0.4.0", optional = true }
yerpc-tide = { git = "https://github.com/Frando/yerpc", optional = true }
[features]
default = []
webserver = ["env_logger", "tide", "tide-websockets", "yerpc-tide"]
[profile.release]
lto = true

View File

@@ -1,75 +0,0 @@
# deltachat-jsonrpc
## Build Requirements
- Linux or Mac, scrips make use of features like `>` pipes and `&&` (maybe the newer versions of powershell support them, but I didn't try that.)
- rust (installed via rustup)
## Start the webserver
The webserver is an example usage. Goal of it is to be usable both as example and as base for deltachat-kaiOS.
```sh
RUST_LOG=info cargo run --features webserver
```
## Generate Typescript Bindings
```sh
cd typescript
npm i
npm run build
```
## Run the development example
Mac
```sh
alias firefox=/Applications/Firefox.app/Contents/MacOS/firefox
npm run example:build && firefox --devtools $(pwd)/example/browser-example.html
```
Linux:
```sh
npm run example:run
```
## Compiling server for kaiOS or android:
```sh
cross build --features=webserver --target armv7-linux-androideabi --release
```
## Run the tests
### Rust tests
```
cargo test --features=webserver
```
### Typescript
```
cd typescript
npm run test
```
For the online tests to run you need a test account token for a mailadm instance,
you can use docker to spin up a local instance: https://github.com/deltachat/docker-mailadm
> set the env var `DCC_NEW_TMP_EMAIL` to your mailadm token: example:
> `DCC_NEW_TMP_EMAIL=https://testrun.org/new_email?t=1h_195dksa6544`
If your test fail with server shutdown at the start, then you might have a process from a last run still running probably and you need to kill that process manually to continue.
#### Test Coverage
You can test coverage with `npm run coverage`, but you need to have `DCC_NEW_TMP_EMAIL` set, otherwise the result will be useless because some functions can only be tested with the online tests.
> If you are offline and want to see the coverage results anyway (even though they are NOT correct), you can bypass the error with `COVERAGE_OFFLINE=1 npm run coverage`
Open `coverage/index.html` for a detailed report.
`bindings.ts` is probably the most interesting file for coverage, because it describes the api functions.

View File

@@ -1,347 +0,0 @@
## Core system
- [X] Base structure of JSON API code
- [X] Implement the first methods for testing + the code that should later be generated by the proc macro
- [X] Create the proc macro
- [X] json api
- [X] ts types
- [X] arguments (no args, one argument, multiple args)
- [X] return type
- [X] custom types as type aliases that ts file looks prettier
## Pre - MVP
- [X] Web socket server
- [WIP] Web socket client (ts)
- [X] backend connection state changed events
- [X] Reconnect on connection loss / connection state
- [ ] find a way to type the event emitter callback functions
- [X] Events
## MVP
- [X] mocha integration test for ts api
- [X] basic tests
- [X] advanced / "online tests" (mailadm for burner accounts)
- [ ] coverage for a majority of the API
- [ ] Blobs served
- [ ] Blob upload (for attachments, setting profile-picture, importing backup and so on)
- [ ] Web push API? At least some kind of notification hook closure this lib can accept.
## Other Ideas
- [ ] make sure there can only be one connection at a time to the ws
- why? , it could give problems if its commanded from multiple connections
- [ ] encrypted connection?
- [ ] authenticated connection?
- [ ] Look into unit-testing for the proc macros?
- [ ] proc macro taking over doc comments to generated typescript file
- [X] GH action for tests (rust and typescript)
- [X] rust test
- [X] rust fmt
- [X] rust clippy
- [X] tsc check
- [X] prettier
- [X] mocha
- [X] scripts to check&fix prettier formatting
## Apis
replicate desktop api feature set:
(this feature set is based on desktop version `1.20`, needs to be updated in the future)
```rs
struct sendMessageParams {
text: Option<String>,
filename: Option<String>, // TODO we need to think about blobs some more
location: Option<(u32,u32)>,
quote_message_id: Option<u32>,
}
struct QrCodeResponse = {
state: u32 // also enum in reality, for simlicity u32 here
id: u32
text1: String
}
impl Api {
// root ---------------------------------------------------------------
// NEEDS_THE_BLOB_QUESTION_ANSWERED_EVENTUALLY
async fn sc_set_profile_picture(&self, new_image: String) -> Result<()> {}
// NEEDS_THE_BLOB_QUESTION_ANSWERED_EVENTUALLY
// 'getProfilePicture' equals to `dc.getContact(C.DC_CONTACT_ID_SELF).getProfileImage()` or `dc.get_config("selfavatar")`
async fn sc_join_secure_join(&self, qrCode: String) -> Result<u32> {}
async fn sc_stop_ongoing_process(&self) -> Result<u32> {}
async fn sc_check_qr_code(&self, qrCode: String) -> Result<QrCodeResponse> {}
// login ----------------------------------------------------
// INFO: login functions need to call stop&start io where applicable
// login.newLogin:
// do instead in frontend:
// 1. call `add_account`
// 2. call `select_account`
// 3. set credentials via set config
// 4. call `sc_configure`
// login.getLogins - is already implemented: `get_all_accounts`
// login.loadAccount - Basically `select_account`
// login.logout -> TODO: unselect account, which isn't implemented in the core yet
// login.forgetAccount -> `remove_account`
// login.getLastLoggedInAccount -> `get_selected_account_id`
// login.updateCredentials -> do instead: set config then call `sc_configure`
// backup -------------------------------------------------------------
// INFO: backup functions need to call stop&start io
// NEEDS_THE_BLOB_QUESTION_ANSWERED_EVENTUALLY
async fn sc_backup_export(&self, out_dir: String) -> Result<()> {}
// NEEDS_THE_BLOB_QUESTION_ANSWERED_EVENTUALLY
async fn sc_backup_import(&self, file: String) -> Result<()> {} // will not return the same as in desktop because this function imports backup to the current context unlike it was in desktop
// chatList -----------------------------------------------------------
// chatList.selectChat - will be removed from desktop
// chatList.getSelectedChatId - will be removed from desktop
// chatList.onChatModified - will be removed from desktop
async fn sc_chatlist_get_general_fresh_message_counter(&self) -> Result<u32> // this method might be used for a favicon badge counter
// contacts ------------------------------------------------------------
async fn sc_contacts_change_nickname(&self, contact_id: u32, new_name: String) -> Result<()>
// contacts.getChatIdByContactId - very similar to sc_contacts_create_chat_by_contact_id
// contacts.getDMChatId - very similar to sc_contacts_create_chat_by_contact_id
async fn sc_contacts_get_encryption_info(&self, contact_id: u32) -> Result<String>
async fn sc_contacts_lookup_contact_id_by_addr(&self, email: String) -> Result<u32>
}
```
```ts
class DeltaRemote {
// chat ---------------------------------------------------------------
call(
fnName: 'chat.getChatMedia',
chatId: number,
msgType1: number,
msgType2: number
): Promise<MessageType[]>
call(fnName: 'chat.getEncryptionInfo', chatId: number): Promise<string>
call(fnName: 'chat.getQrCode', chatId?: number): Promise<string>
call(fnName: 'chat.leaveGroup', chatId: number): Promise<void>
call(fnName: 'chat.setName', chatId: number, name: string): Promise<boolean>
call(
fnName: 'chat.modifyGroup',
chatId: number,
name: string,
image: string,
remove: number[],
add: number[]
): Promise<boolean>
call(
fnName: 'chat.addContactToChat',
chatId: number,
contactId: number
): Promise<boolean>
call(
fnName: 'chat.setProfileImage',
chatId: number,
newImage: string
): Promise<boolean>
call(
fnName: 'chat.setMuteDuration',
chatId: number,
duration: MuteDuration
): Promise<boolean>
call(
fnName: 'chat.createGroupChat',
verified: boolean,
name: string
): Promise<number>
call(fnName: 'chat.delete', chatId: number): Promise<void>
call(
fnName: 'chat.setVisibility',
chatId: number,
visibility:
| C.DC_CERTCK_AUTO
| C.DC_CERTCK_STRICT
| C.DC_CHAT_VISIBILITY_PINNED
): Promise<void>
call(fnName: 'chat.getChatContacts', chatId: number): Promise<number[]>
call(fnName: 'chat.markNoticedChat', chatId: number): Promise<void>
call(fnName: 'chat.getChatEphemeralTimer', chatId: number): Promise<number>
call(
fnName: 'chat.setChatEphemeralTimer',
chatId: number,
ephemeralTimer: number
): Promise<void>
call(fnName: 'chat.sendVideoChatInvitation', chatId: number): Promise<number>
call(
fnName: 'chat.decideOnContactRequest',
messageId: number,
decision:
| C.DC_DECISION_START_CHAT
| C.DC_DECISION_NOT_NOW
| C.DC_DECISION_BLOCK
): Promise<number>
// locations ----------------------------------------------------------
call(
fnName: 'locations.setLocation',
latitude: number,
longitude: number,
accuracy: number
): Promise<void>
call(
fnName: 'locations.getLocations',
chatId: number,
contactId: number,
timestampFrom: number,
timestampTo: number
): Promise<JsonLocations>
// NOTHING HERE that is called directly from the frontend, yet
// messageList --------------------------------------------------------
call(
fnName: 'messageList.sendMessage',
chatId: number,
params: sendMessageParams
): Promise<[number, MessageType | null]>
call(
fnName: 'messageList.sendSticker',
chatId: number,
stickerPath: string
): Promise<void>
call(fnName: 'messageList.deleteMessage', id: number): Promise<void>
call(fnName: 'messageList.getMessageInfo', msgId: number): Promise<string>
call(
fnName: 'messageList.getDraft',
chatId: number
): Promise<MessageType | null>
call(
fnName: 'messageList.setDraft',
chatId: number,
{
text,
file,
quotedMessageId,
}: { text?: string; file?: string; quotedMessageId?: number }
): Promise<void>
call(
fnName: 'messageList.messageIdToJson',
id: number
): Promise<{ msg: null } | MessageType>
call(
fnName: 'messageList.forwardMessage',
msgId: number,
chatId: number
): Promise<void>
call(
fnName: 'messageList.searchMessages',
query: string,
chatId?: number
): Promise<number[]>
call(
fnName: 'messageList.msgIds2SearchResultItems',
msgIds: number[]
): Promise<{ [id: number]: MessageSearchResult }>
call(
fnName: 'messageList.saveMessageHTML2Disk',
messageId: number
): Promise<string>
// settings -----------------------------------------------------------
call(fnName: 'settings.keysImport', directory: string): Promise<void>
call(fnName: 'settings.keysExport', directory: string): Promise<void>
call(
fnName: 'settings.serverFlags',
{
mail_security,
send_security,
}: {
mail_security?: string
send_security?: string
}
): Promise<number | ''>
call(
fnName: 'settings.setDesktopSetting',
key: keyof DesktopSettings,
value: string | number | boolean
): Promise<boolean>
call(fnName: 'settings.getDesktopSettings'): Promise<DesktopSettings>
call(
fnName: 'settings.saveBackgroundImage',
file: string,
isDefaultPicture: boolean
): Promise<string>
call(
fnName: 'settings.estimateAutodeleteCount',
fromServer: boolean,
seconds: number
): Promise<number>
// stickers -----------------------------------------------------------
call(
fnName: 'stickers.getStickers'
): Promise<{
[key: string]: string[]
}> // todo move to extras? because its not directly elated to core
// context ------------------------------------------------------------
call(fnName: 'context.maybeNetwork'): Promise<void>
// burner accounts ------------------------------------------------------------
call(
fnName: 'burnerAccounts.create',
url: string
): Promise<{ email: string; password: string }> // think about how to improve that api - probably use core api instead
// extras -------------------------------------------------------------
call(fnName: 'extras.getLocaleData', locale: string): Promise<LocaleData>
call(fnName: 'extras.setLocale', locale: string): Promise<void>
call(
fnName: 'extras.getActiveTheme'
): Promise<{
theme: Theme
data: string
} | null>
call(fnName: 'extras.setThemeFilePath', address: string): void
call(fnName: 'extras.getAvailableThemes'): Promise<Theme[]>
call(fnName: 'extras.setTheme', address: string): Promise<boolean>
// catchall: ----------------------------------------------------------
call(fnName: string): Promise<any>
call(fnName: string, ...args: any[]): Promise<any> {
return _callDcMethodAsync(fnName, ...args)
}
}
export const DeltaBackend = new DeltaRemote()
```
after that, or while doing it adjust api to be more complete
TODO different test to simulate two devices:
to test autocrypt_initiate_key_transfer & autocrypt_continue_key_transfer

View File

@@ -1,154 +0,0 @@
use deltachat::{Event, EventType};
use serde::Serialize;
use serde_json::{json, Value};
use typescript_type_def::TypeDef;
pub fn event_to_json_rpc_notification(event: Event) -> Value {
let (field1, field2): (Value, Value) = match &event.typ {
// events with a single string in field1
EventType::Info(txt)
| EventType::SmtpConnected(txt)
| EventType::ImapConnected(txt)
| EventType::SmtpMessageSent(txt)
| EventType::ImapMessageDeleted(txt)
| EventType::ImapMessageMoved(txt)
| EventType::NewBlobFile(txt)
| EventType::DeletedBlobFile(txt)
| EventType::Warning(txt)
| EventType::Error(txt)
| EventType::ErrorSelfNotInGroup(txt) => (json!(txt), Value::Null),
EventType::ImexFileWritten(path) => (json!(path.to_str()), Value::Null),
// single number
EventType::MsgsNoticed(chat_id) | EventType::ChatModified(chat_id) => {
(json!(chat_id), Value::Null)
}
EventType::ImexProgress(progress) => (json!(progress), Value::Null),
// both fields contain numbers
EventType::MsgsChanged { chat_id, msg_id }
| EventType::IncomingMsg { chat_id, msg_id }
| EventType::MsgDelivered { chat_id, msg_id }
| EventType::MsgFailed { chat_id, msg_id }
| EventType::MsgRead { chat_id, msg_id } => (json!(chat_id), json!(msg_id)),
EventType::ChatEphemeralTimerModified { chat_id, timer } => (json!(chat_id), json!(timer)),
EventType::SecurejoinInviterProgress {
contact_id,
progress,
}
| EventType::SecurejoinJoinerProgress {
contact_id,
progress,
} => (json!(contact_id), json!(progress)),
// field 1 number or null
EventType::ContactsChanged(maybe_number) | EventType::LocationChanged(maybe_number) => (
match maybe_number {
Some(number) => json!(number),
None => Value::Null,
},
Value::Null,
),
// number and maybe string
EventType::ConfigureProgress { progress, comment } => (
json!(progress),
match comment {
Some(content) => json!(content),
None => Value::Null,
},
),
EventType::ConnectivityChanged => (Value::Null, Value::Null),
EventType::SelfavatarChanged => (Value::Null, Value::Null),
EventType::WebxdcStatusUpdate {
msg_id,
status_update_serial,
} => (json!(msg_id), json!(status_update_serial)),
};
json!({
"id": event_type_to_string(event.typ),
"contextId": event.id,
"field1": field1,
"field2": field2
})
}
#[derive(Serialize, TypeDef)]
pub enum EventTypeName {
Info,
SmtpConnected,
ImapConnected,
SmtpMessageSent,
ImapMessageDeleted,
ImapMessageMoved,
NewBlobFile,
DeletedBlobFile,
Warning,
Error,
ErrorSelfNotInGroup,
MsgsChanged,
IncomingMsg,
MsgsNoticed,
MsgDelivered,
MsgFailed,
MsgRead,
ChatModified,
ChatEphemeralTimerModified,
ContactsChanged,
LocationChanged,
ConfigureProgress,
ImexProgress,
ImexFileWritten,
SecurejoinInviterProgress,
SecurejoinJoinerProgress,
ConnectivityChanged,
SelfavatarChanged,
WebxdcStatusUpdate,
}
fn event_type_to_string(event: EventType) -> EventTypeName {
use EventTypeName::*;
match event {
EventType::Info(_) => Info,
EventType::SmtpConnected(_) => SmtpConnected,
EventType::ImapConnected(_) => ImapConnected,
EventType::SmtpMessageSent(_) => SmtpMessageSent,
EventType::ImapMessageDeleted(_) => ImapMessageDeleted,
EventType::ImapMessageMoved(_) => ImapMessageMoved,
EventType::NewBlobFile(_) => NewBlobFile,
EventType::DeletedBlobFile(_) => DeletedBlobFile,
EventType::Warning(_) => Warning,
EventType::Error(_) => Error,
EventType::ErrorSelfNotInGroup(_) => ErrorSelfNotInGroup,
EventType::MsgsChanged { .. } => MsgsChanged,
EventType::IncomingMsg { .. } => IncomingMsg,
EventType::MsgsNoticed(_) => MsgsNoticed,
EventType::MsgDelivered { .. } => MsgDelivered,
EventType::MsgFailed { .. } => MsgFailed,
EventType::MsgRead { .. } => MsgRead,
EventType::ChatModified(_) => ChatModified,
EventType::ChatEphemeralTimerModified { .. } => ChatEphemeralTimerModified,
EventType::ContactsChanged(_) => ContactsChanged,
EventType::LocationChanged(_) => LocationChanged,
EventType::ConfigureProgress { .. } => ConfigureProgress,
EventType::ImexProgress(_) => ImexProgress,
EventType::ImexFileWritten(_) => ImexFileWritten,
EventType::SecurejoinInviterProgress { .. } => SecurejoinInviterProgress,
EventType::SecurejoinJoinerProgress { .. } => SecurejoinJoinerProgress,
EventType::ConnectivityChanged => ConnectivityChanged,
EventType::SelfavatarChanged => SelfavatarChanged,
EventType::WebxdcStatusUpdate { .. } => WebxdcStatusUpdate,
}
}
#[cfg(test)]
#[test]
fn generate_events_ts_types_definition() {
let events = {
let mut buf = Vec::new();
let options = typescript_type_def::DefinitionFileOptions {
root_namespace: None,
..typescript_type_def::DefinitionFileOptions::default()
};
typescript_type_def::write_definition_file::<_, EventTypeName>(&mut buf, options).unwrap();
String::from_utf8(buf).unwrap()
};
std::fs::write("typescript/generated/events.ts", events).unwrap();
}

View File

@@ -1,532 +0,0 @@
use anyhow::{anyhow, bail, Context, Result};
use async_std::sync::{Arc, RwLock};
use deltachat::{
chat::{get_chat_msgs, ChatId},
chatlist::Chatlist,
config::Config,
contact::{may_be_valid_addr, Contact, ContactId},
context::get_info,
message::{Message, MsgId, Viewtype},
provider::get_provider_info,
};
use std::collections::BTreeMap;
use std::{collections::HashMap, str::FromStr};
use yerpc::rpc;
pub use deltachat::accounts::Accounts;
pub mod events;
pub mod types;
use crate::api::types::chat_list::{get_chat_list_item_by_id, ChatListItemFetchResult};
use types::account::Account;
use types::chat::FullChat;
use types::chat_list::ChatListEntry;
use types::contact::ContactObject;
use types::message::MessageObject;
use types::provider_info::ProviderInfo;
#[derive(Clone, Debug)]
pub struct CommandApi {
pub(crate) accounts: Arc<RwLock<Accounts>>,
}
impl CommandApi {
pub fn new(accounts: Accounts) -> Self {
CommandApi {
accounts: Arc::new(RwLock::new(accounts)),
}
}
async fn get_context(&self, id: u32) -> Result<deltachat::context::Context> {
let sc = self
.accounts
.read()
.await
.get_account(id)
.await
.ok_or_else(|| anyhow!("account with id {} not found", id))?;
Ok(sc)
}
}
#[rpc(all_positional, ts_outdir = "typescript/generated")]
impl CommandApi {
// ---------------------------------------------
// Misc top level functions
// ---------------------------------------------
/// Check if an email address is valid.
async fn check_email_validity(&self, email: String) -> bool {
may_be_valid_addr(&email)
}
/// Get general system info.
async fn get_system_info(&self) -> BTreeMap<&'static str, String> {
get_info()
}
// ---------------------------------------------
// Account Management
// ---------------------------------------------
async fn add_account(&self) -> Result<u32> {
self.accounts.write().await.add_account().await
}
async fn remove_account(&self, account_id: u32) -> Result<()> {
self.accounts.write().await.remove_account(account_id).await
}
async fn get_all_account_ids(&self) -> Vec<u32> {
self.accounts.read().await.get_all().await
}
/// Select account id for internally selected state.
/// TODO: Likely this is deprecated as all methods take an account id now.
async fn select_account(&self, id: u32) -> Result<()> {
self.accounts.write().await.select_account(id).await
}
/// Get the selected account id of the internal state..
/// TODO: Likely this is deprecated as all methods take an account id now.
async fn get_selected_account_id(&self) -> Option<u32> {
self.accounts.read().await.get_selected_account_id().await
}
/// Get a list of all configured accounts.
async fn get_all_accounts(&self) -> Result<Vec<Account>> {
let mut accounts = Vec::new();
for id in self.accounts.read().await.get_all().await {
let context_option = self.accounts.read().await.get_account(id).await;
if let Some(ctx) = context_option {
accounts.push(Account::from_context(&ctx, id).await?)
} else {
println!("account with id {} doesn't exist anymore", id);
}
}
Ok(accounts)
}
// ---------------------------------------------
// Methods that work on individual accounts
// ---------------------------------------------
/// Get top-level info for an account.
async fn get_account_info(&self, account_id: u32) -> Result<Account> {
let context_option = self.accounts.read().await.get_account(account_id).await;
if let Some(ctx) = context_option {
Ok(Account::from_context(&ctx, account_id).await?)
} else {
Err(anyhow!(
"account with id {} doesn't exist anymore",
account_id
))
}
}
/// Returns provider for the given domain.
///
/// This function looks up domain in offline database.
///
/// For compatibility, email address can be passed to this function
/// instead of the domain.
async fn get_provider_info(
&self,
account_id: u32,
email: String,
) -> Result<Option<ProviderInfo>> {
let ctx = self.get_context(account_id).await?;
let socks5_enabled = ctx
.get_config_bool(deltachat::config::Config::Socks5Enabled)
.await?;
let provider_info =
get_provider_info(&ctx, email.split('@').last().unwrap_or(""), socks5_enabled).await;
Ok(ProviderInfo::from_dc_type(provider_info))
}
/// Checks if the context is already configured.
async fn is_configured(&self, account_id: u32) -> Result<bool> {
let ctx = self.get_context(account_id).await?;
ctx.is_configured().await
}
/// Get system info for an account.
async fn get_info(&self, account_id: u32) -> Result<BTreeMap<&'static str, String>> {
let ctx = self.get_context(account_id).await?;
ctx.get_info().await
}
async fn set_config(&self, account_id: u32, key: String, value: Option<String>) -> Result<()> {
let ctx = self.get_context(account_id).await?;
set_config(&ctx, &key, value.as_deref()).await
}
async fn batch_set_config(
&self,
account_id: u32,
config: HashMap<String, Option<String>>,
) -> Result<()> {
let ctx = self.get_context(account_id).await?;
for (key, value) in config.into_iter() {
set_config(&ctx, &key, value.as_deref())
.await
.with_context(|| format!("Can't set {} to {:?}", key, value))?;
}
Ok(())
}
async fn get_config(&self, account_id: u32, key: String) -> Result<Option<String>> {
let ctx = self.get_context(account_id).await?;
get_config(&ctx, &key).await
}
async fn batch_get_config(
&self,
account_id: u32,
keys: Vec<String>,
) -> Result<HashMap<String, Option<String>>> {
let ctx = self.get_context(account_id).await?;
let mut result: HashMap<String, Option<String>> = HashMap::new();
for key in keys {
result.insert(key.clone(), get_config(&ctx, &key).await?);
}
Ok(result)
}
/// Configures this account with the currently set parameters.
/// Setup the credential config before calling this.
async fn configure(&self, account_id: u32) -> Result<()> {
let ctx = self.get_context(account_id).await?;
ctx.stop_io().await;
ctx.configure().await?;
ctx.start_io().await;
Ok(())
}
/// Signal an ongoing process to stop.
async fn stop_ongoing_process(&self, account_id: u32) -> Result<()> {
let ctx = self.get_context(account_id).await?;
ctx.stop_ongoing().await;
Ok(())
}
// ---------------------------------------------
// autocrypt
// ---------------------------------------------
async fn autocrypt_initiate_key_transfer(&self, account_id: u32) -> Result<String> {
let ctx = self.get_context(account_id).await?;
deltachat::imex::initiate_key_transfer(&ctx).await
}
async fn autocrypt_continue_key_transfer(
&self,
account_id: u32,
message_id: u32,
setup_code: String,
) -> Result<()> {
let ctx = self.get_context(account_id).await?;
deltachat::imex::continue_key_transfer(&ctx, MsgId::new(message_id), &setup_code).await
}
// ---------------------------------------------
// chat list
// ---------------------------------------------
async fn get_chatlist_entries(
&self,
account_id: u32,
list_flags: Option<u32>,
query_string: Option<String>,
query_contact_id: Option<u32>,
) -> Result<Vec<ChatListEntry>> {
let ctx = self.get_context(account_id).await?;
let list = Chatlist::try_load(
&ctx,
list_flags.unwrap_or(0) as usize,
query_string.as_deref(),
query_contact_id.map(ContactId::new),
)
.await?;
let mut l: Vec<ChatListEntry> = Vec::new();
for i in 0..list.len() {
l.push(ChatListEntry(
list.get_chat_id(i)?.to_u32(),
list.get_msg_id(i)?.unwrap_or_default().to_u32(),
));
}
Ok(l)
}
async fn get_chatlist_items_by_entries(
&self,
account_id: u32,
entries: Vec<ChatListEntry>,
) -> Result<HashMap<u32, ChatListItemFetchResult>> {
// todo custom json deserializer for ChatListEntry?
let ctx = self.get_context(account_id).await?;
let mut result: HashMap<u32, ChatListItemFetchResult> = HashMap::new();
for (_i, entry) in entries.iter().enumerate() {
result.insert(
entry.0,
match get_chat_list_item_by_id(&ctx, entry).await {
Ok(res) => res,
Err(err) => ChatListItemFetchResult::Error {
id: entry.0,
error: format!("{:?}", err),
},
},
);
}
Ok(result)
}
// ---------------------------------------------
// chat
// ---------------------------------------------
async fn chatlist_get_full_chat_by_id(
&self,
account_id: u32,
chat_id: u32,
) -> Result<FullChat> {
let ctx = self.get_context(account_id).await?;
FullChat::from_dc_chat_id(&ctx, chat_id).await
}
async fn accept_chat(&self, account_id: u32, chat_id: u32) -> Result<()> {
let ctx = self.get_context(account_id).await?;
ChatId::new(chat_id).accept(&ctx).await
}
async fn block_chat(&self, account_id: u32, chat_id: u32) -> Result<()> {
let ctx = self.get_context(account_id).await?;
ChatId::new(chat_id).block(&ctx).await
}
// ---------------------------------------------
// message list
// ---------------------------------------------
async fn message_list_get_message_ids(
&self,
account_id: u32,
chat_id: u32,
flags: u32,
) -> Result<Vec<u32>> {
let ctx = self.get_context(account_id).await?;
let msg = get_chat_msgs(&ctx, ChatId::new(chat_id), flags).await?;
Ok(msg
.iter()
.filter_map(|chat_item| match chat_item {
deltachat::chat::ChatItem::Message { msg_id } => Some(msg_id.to_u32()),
_ => None,
})
.collect())
}
async fn message_get_message(&self, account_id: u32, message_id: u32) -> Result<MessageObject> {
let ctx = self.get_context(account_id).await?;
MessageObject::from_message_id(&ctx, message_id).await
}
async fn message_get_messages(
&self,
account_id: u32,
message_ids: Vec<u32>,
) -> Result<HashMap<u32, MessageObject>> {
let ctx = self.get_context(account_id).await?;
let mut messages: HashMap<u32, MessageObject> = HashMap::new();
for message_id in message_ids {
messages.insert(
message_id,
MessageObject::from_message_id(&ctx, message_id).await?,
);
}
Ok(messages)
}
// ---------------------------------------------
// contact
// ---------------------------------------------
/// Get a single contact options by ID.
async fn contacts_get_contact(
&self,
account_id: u32,
contact_id: u32,
) -> Result<ContactObject> {
let ctx = self.get_context(account_id).await?;
let contact_id = ContactId::new(contact_id);
ContactObject::from_dc_contact(
&ctx,
deltachat::contact::Contact::get_by_id(&ctx, contact_id).await?,
)
.await
}
/// Add a single contact as a result of an explicit user action.
///
/// Returns contact id of the created or existing contact
async fn contacts_create_contact(
&self,
account_id: u32,
email: String,
name: Option<String>,
) -> Result<u32> {
let ctx = self.get_context(account_id).await?;
if !may_be_valid_addr(&email) {
bail!(anyhow!(
"provided email address is not a valid email address"
))
}
let contact_id = Contact::create(&ctx, &name.unwrap_or_default(), &email).await?;
Ok(contact_id.to_u32())
}
/// Returns contact id of the created or existing DM chat with that contact
async fn contacts_create_chat_by_contact_id(
&self,
account_id: u32,
contact_id: u32,
) -> Result<u32> {
let ctx = self.get_context(account_id).await?;
let contact = Contact::get_by_id(&ctx, ContactId::new(contact_id)).await?;
ChatId::create_for_contact(&ctx, contact.id)
.await
.map(|id| id.to_u32())
}
async fn contacts_block(&self, account_id: u32, contact_id: u32) -> Result<()> {
let ctx = self.get_context(account_id).await?;
Contact::block(&ctx, ContactId::new(contact_id)).await
}
async fn contacts_unblock(&self, account_id: u32, contact_id: u32) -> Result<()> {
let ctx = self.get_context(account_id).await?;
Contact::unblock(&ctx, ContactId::new(contact_id)).await
}
async fn contacts_get_blocked(&self, account_id: u32) -> Result<Vec<ContactObject>> {
let ctx = self.get_context(account_id).await?;
let blocked_ids = Contact::get_all_blocked(&ctx).await?;
let mut contacts: Vec<ContactObject> = Vec::with_capacity(blocked_ids.len());
for id in blocked_ids {
contacts.push(
ContactObject::from_dc_contact(
&ctx,
deltachat::contact::Contact::get_by_id(&ctx, id).await?,
)
.await?,
);
}
Ok(contacts)
}
async fn contacts_get_contact_ids(
&self,
account_id: u32,
list_flags: u32,
query: Option<String>,
) -> Result<Vec<u32>> {
let ctx = self.get_context(account_id).await?;
let contacts = Contact::get_all(&ctx, list_flags, query.as_deref()).await?;
Ok(contacts.into_iter().map(|c| c.to_u32()).collect())
}
/// Get a list of contacts.
/// (formerly called getContacts2 in desktop)
async fn contacts_get_contacts(
&self,
account_id: u32,
list_flags: u32,
query: Option<String>,
) -> Result<Vec<ContactObject>> {
let ctx = self.get_context(account_id).await?;
let contact_ids = Contact::get_all(&ctx, list_flags, query.as_deref()).await?;
let mut contacts: Vec<ContactObject> = Vec::with_capacity(contact_ids.len());
for id in contact_ids {
contacts.push(
ContactObject::from_dc_contact(
&ctx,
deltachat::contact::Contact::get_by_id(&ctx, id).await?,
)
.await?,
);
}
Ok(contacts)
}
async fn contacts_get_contacts_by_ids(
&self,
account_id: u32,
ids: Vec<u32>,
) -> Result<HashMap<u32, ContactObject>> {
let ctx = self.get_context(account_id).await?;
let mut contacts = HashMap::with_capacity(ids.len());
for id in ids {
contacts.insert(
id,
ContactObject::from_dc_contact(
&ctx,
deltachat::contact::Contact::get_by_id(&ctx, ContactId::new(id)).await?,
)
.await?,
);
}
Ok(contacts)
}
// ---------------------------------------------
// misc prototyping functions
// that might get removed later again
// ---------------------------------------------
/// Returns the messageid of the sent message
async fn misc_send_text_message(
&self,
account_id: u32,
text: String,
chat_id: u32,
) -> Result<u32> {
let ctx = self.get_context(account_id).await?;
let mut msg = Message::new(Viewtype::Text);
msg.set_text(Some(text));
let message_id = deltachat::chat::send_msg(&ctx, ChatId::new(chat_id), &mut msg).await?;
Ok(message_id.to_u32())
}
}
// Helper functions (to prevent code duplication)
async fn set_config(
ctx: &deltachat::context::Context,
key: &str,
value: Option<&str>,
) -> Result<(), anyhow::Error> {
if key.starts_with("ui.") {
ctx.set_ui_config(key, value).await
} else {
ctx.set_config(Config::from_str(key).context("unknown key")?, value)
.await
}
}
async fn get_config(
ctx: &deltachat::context::Context,
key: &str,
) -> Result<Option<String>, anyhow::Error> {
if key.starts_with("ui.") {
ctx.get_ui_config(key).await
} else {
ctx.get_config(Config::from_str(key).context("unknown key")?)
.await
}
}

View File

@@ -1,46 +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)]
#[serde(tag = "type")]
pub enum Account {
//#[serde(rename_all = "camelCase")]
Configured {
id: u32,
display_name: Option<String>,
addr: Option<String>,
// size: u32,
profile_image: Option<String>, // TODO: This needs to be converted to work with blob http server.
color: String,
},
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_color(),
);
Ok(Account::Configured {
id,
display_name,
addr,
profile_image,
color,
})
} else {
Ok(Account::Unconfigured { id })
}
}
}

View File

@@ -1,91 +0,0 @@
use anyhow::{anyhow, Result};
use deltachat::chat::get_chat_contacts;
use deltachat::chat::{Chat, ChatId};
use deltachat::contact::{Contact, ContactId};
use deltachat::context::Context;
use num_traits::cast::ToPrimitive;
use serde::Serialize;
use typescript_type_def::TypeDef;
use super::color_int_to_hex_string;
use super::contact::ContactObject;
#[derive(Serialize, TypeDef)]
pub struct FullChat {
id: u32,
name: String,
is_protected: bool,
profile_image: Option<String>, //BLOBS ?
archived: bool,
// subtitle - will be moved to frontend because it uses translation functions
chat_type: u32,
is_unpromoted: bool,
is_self_talk: bool,
contacts: Vec<ContactObject>,
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,
self_in_group: bool,
is_muted: bool,
ephemeral_timer: u32, //TODO look if there are more important properties in newer core versions
can_send: bool,
}
impl FullChat {
pub async fn 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 mut contacts = Vec::new();
for contact_id in &contact_ids {
contacts.push(
ContactObject::from_dc_contact(
context,
Contact::load_from_db(context, *contact_id).await?,
)
.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?;
Ok(FullChat {
id: chat_id,
name: chat.name.clone(),
is_protected: chat.is_protected(),
profile_image, //BLOBS ?
archived: chat.get_visibility() == deltachat::chat::ChatVisibility::Archived,
chat_type: chat
.get_type()
.to_u32()
.ok_or_else(|| anyhow!("unknown chat type id"))?, // TODO get rid of this unwrap?
is_unpromoted: chat.is_unpromoted(),
is_self_talk: chat.is_self_talk(),
contacts,
contact_ids: 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,
})
}
}

View File

@@ -1,117 +0,0 @@
use anyhow::Result;
use deltachat::constants::*;
use deltachat::contact::ContactId;
use deltachat::{
chat::{get_chat_contacts, ChatVisibility},
chatlist::Chatlist,
};
use deltachat::{
chat::{Chat, ChatId},
message::MsgId,
};
use num_traits::cast::ToPrimitive;
use serde::{Deserialize, Serialize};
use typescript_type_def::TypeDef;
use super::color_int_to_hex_string;
#[derive(Deserialize, Serialize, TypeDef)]
pub struct ChatListEntry(pub u32, pub u32);
#[derive(Serialize, TypeDef)]
#[serde(tag = "type")]
pub enum ChatListItemFetchResult {
#[serde(rename_all = "camelCase")]
ChatListItem {
id: u32,
name: String,
avatar_path: Option<String>,
color: String,
last_updated: Option<i64>,
summary_text1: String,
summary_text2: String,
summary_status: u32,
is_protected: bool,
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,
},
ArchiveLink,
#[serde(rename_all = "camelCase")]
Error {
id: u32,
error: String,
},
}
pub(crate) async fn get_chat_list_item_by_id(
ctx: &deltachat::context::Context,
entry: &ChatListEntry,
) -> Result<ChatListItemFetchResult> {
let chat_id = ChatId::new(entry.0);
let last_msgid = match entry.1 {
0 => None,
_ => Some(MsgId::new(entry.1)),
};
if chat_id.is_archived_link() {
return Ok(ChatListItemFetchResult::ArchiveLink);
}
let chat = Chat::load_from_db(ctx, chat_id).await?;
let summary = Chatlist::get_summary2(ctx, chat_id, last_msgid, Some(&chat)).await?;
let summary_text1 = summary.prefix.map_or_else(String::new, |s| s.to_string());
let summary_text2 = summary.text.to_owned();
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 = match last_msgid {
Some(id) => {
let last_message = deltachat::message::Message::load_from_db(ctx, id).await?;
Some(last_message.get_timestamp() * 1000)
}
None => None,
};
let self_in_group = get_chat_contacts(ctx, chat_id)
.await?
.contains(&ContactId::SELF);
let fresh_message_counter = chat_id.get_fresh_msg_cnt(ctx).await?;
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,
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
is_protected: chat.is_protected(),
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: self_in_group,
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(),
})
}

View File

@@ -1,50 +0,0 @@
use anyhow::Result;
use deltachat::contact::VerifiedStatus;
use deltachat::context::Context;
use serde::Serialize;
use typescript_type_def::TypeDef;
use super::color_int_to_hex_string;
#[derive(Serialize, TypeDef)]
#[serde(rename = "Contact")]
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_verified: bool,
}
impl ContactObject {
pub async fn 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? == VerifiedStatus::BidirectVerified;
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_verified,
})
}
}

View File

@@ -1,127 +0,0 @@
use anyhow::{anyhow, Result};
use deltachat::contact::Contact;
use deltachat::context::Context;
use deltachat::message::Message;
use deltachat::message::MsgId;
use num_traits::cast::ToPrimitive;
use serde::Serialize;
use typescript_type_def::TypeDef;
use super::contact::ContactObject;
#[derive(Serialize, TypeDef)]
#[serde(rename = "Message")]
pub struct MessageObject {
id: u32,
chat_id: u32,
from_id: u32,
quoted_text: Option<String>,
quoted_message_id: Option<u32>,
text: Option<String>,
has_location: bool,
has_html: bool,
view_type: u32,
state: u32,
timestamp: i64,
sort_timestamp: i64,
received_timestamp: i64,
has_deviating_timestamp: bool,
// summary - use/create another function if you need it
subject: String,
show_padlock: bool,
is_setupmessage: bool,
is_info: bool,
is_forwarded: bool,
duration: i32,
dimensions_height: i32,
dimensions_width: i32,
videochat_type: Option<u32>,
videochat_url: Option<String>,
override_sender_name: Option<String>,
sender: ContactObject,
setup_code_begin: Option<String>,
file: Option<String>,
file_mime: Option<String>,
file_bytes: u64,
file_name: Option<String>,
}
impl MessageObject {
pub async fn from_message_id(context: &Context, message_id: u32) -> Result<Self> {
let msg_id = MsgId::new(message_id);
let message = Message::load_from_db(context, msg_id).await?;
let quoted_message_id = message
.quoted_message(context)
.await?
.map(|m| m.get_id().to_u32());
let sender_contact = Contact::load_from_db(context, message.get_from_id()).await?;
let sender = ContactObject::from_dc_contact(context, sender_contact).await?;
let file_bytes = message.get_filebytes(context).await;
let override_sender_name = message.get_override_sender_name();
Ok(MessageObject {
id: message_id,
chat_id: message.get_chat_id().to_u32(),
from_id: message.get_from_id().to_u32(),
quoted_text: message.quoted_text(),
quoted_message_id,
text: message.get_text(),
has_location: message.has_location(),
has_html: message.has_html(),
view_type: message
.get_viewtype()
.to_u32()
.ok_or_else(|| anyhow!("viewtype conversion to number failed"))?,
state: message
.get_state()
.to_u32()
.ok_or_else(|| anyhow!("state conversion to number failed"))?,
timestamp: message.get_timestamp(),
sort_timestamp: message.get_sort_timestamp(),
received_timestamp: message.get_received_timestamp(),
has_deviating_timestamp: message.has_deviating_timestamp(),
subject: message.get_subject().to_owned(),
show_padlock: message.get_showpadlock(),
is_setupmessage: message.is_setupmessage(),
is_info: message.is_info(),
is_forwarded: message.is_forwarded(),
duration: message.get_duration(),
dimensions_height: message.get_height(),
dimensions_width: message.get_width(),
videochat_type: match message.get_videochat_type() {
Some(vct) => Some(
vct.to_u32()
.ok_or_else(|| anyhow!("state conversion to number failed"))?,
),
None => None,
},
videochat_url: message.get_videochat_url(),
override_sender_name,
sender,
setup_code_begin: message.get_setupcodebegin(context).await,
file: match message.get_file(context) {
Some(path_buf) => path_buf.to_str().map(|s| s.to_owned()),
None => None,
}, //BLOBS
file_mime: message.get_filemime(),
file_bytes,
file_name: message.get_filename(),
})
}
}

View File

@@ -1,10 +0,0 @@
pub mod account;
pub mod chat;
pub mod chat_list;
pub mod contact;
pub mod message;
pub mod provider_info;
pub fn color_int_to_hex_string(color: u32) -> String {
format!("{:#08x}", color).replace("0x", "#")
}

View File

@@ -1,21 +0,0 @@
use deltachat::provider::Provider;
use num_traits::cast::ToPrimitive;
use serde::Serialize;
use typescript_type_def::TypeDef;
#[derive(Serialize, TypeDef)]
pub struct ProviderInfo {
pub before_login_hint: String,
pub overview_page: String,
pub status: u32, // in reality this is an enum, but for simlicity and because it gets converted into a number anyway, we use an u32 here.
}
impl ProviderInfo {
pub fn from_dc_type(provider: Option<&Provider>) -> Option<Self> {
provider.map(|p| ProviderInfo {
before_login_hint: p.before_login_hint.to_owned(),
overview_page: p.overview_page.to_owned(),
status: p.status.to_u32().unwrap(),
})
}
}

View File

@@ -1,58 +0,0 @@
pub mod api;
pub use api::events;
#[cfg(test)]
mod tests {
use super::api::{Accounts, CommandApi};
use async_channel::unbounded;
use async_std::task;
use futures::StreamExt;
use tempfile::TempDir;
use yerpc::{MessageHandle, RpcHandle};
#[async_std::test]
async fn basic_json_rpc_functionality() -> anyhow::Result<()> {
// println!("{}", "");
let tmp_dir = TempDir::new().unwrap().path().into();
println!("tmp_dir: {:?}", tmp_dir);
let accounts = Accounts::new(tmp_dir).await?;
let cmd_api = CommandApi::new(accounts);
let (sender, mut receiver) = unbounded::<String>();
let (request_handle, mut rx) = RpcHandle::new();
let session = cmd_api;
let handle = MessageHandle::new(request_handle, session);
task::spawn({
async move {
while let Some(message) = rx.next().await {
let message = serde_json::to_string(&message)?;
// Abort serialization on error.
sender.send(message).await?;
}
let res: Result<(), anyhow::Error> = Ok(());
res
}
});
{
let request = r#"{"jsonrpc":"2.0","method":"add_account","params":[],"id":1}"#;
let response = r#"{"jsonrpc":"2.0","id":1,"result":1}"#;
handle.handle_message(request).await;
let result = receiver.next().await;
println!("{:?}", result);
assert_eq!(result, Some(response.to_owned()));
}
{
let request = r#"{"jsonrpc":"2.0","method":"get_all_account_ids","params":[],"id":2}"#;
let response = r#"{"jsonrpc":"2.0","id":2,"result":[1]}"#;
handle.handle_message(request).await;
let result = receiver.next().await;
println!("{:?}", result);
assert_eq!(result, Some(response.to_owned()));
}
Ok(())
}
}

View File

@@ -1,44 +0,0 @@
use async_std::path::PathBuf;
use async_std::task;
use tide::Request;
use yerpc::RpcHandle;
use yerpc_tide::yerpc_handler;
mod api;
use api::events::event_to_json_rpc_notification;
use api::{Accounts, CommandApi};
#[async_std::main]
async fn main() -> Result<(), std::io::Error> {
env_logger::init();
log::info!("Starting");
let accounts = Accounts::new(PathBuf::from("./accounts")).await.unwrap();
let state = CommandApi::new(accounts);
let mut app = tide::with_state(state.clone());
app.at("/ws").get(yerpc_handler(request_handler));
state.accounts.read().await.start_io().await;
app.listen("127.0.0.1:20808").await?;
Ok(())
}
async fn request_handler(
request: Request<CommandApi>,
rpc: RpcHandle,
) -> anyhow::Result<CommandApi> {
let state = request.state().clone();
task::spawn(event_loop(state.clone(), rpc));
Ok(state)
}
async fn event_loop(state: CommandApi, rpc: RpcHandle) -> anyhow::Result<()> {
let events = state.accounts.read().await.get_event_emitter().await;
while let Some(event) = events.recv().await {
// log::debug!("event {:?}", event);
let event = event_to_json_rpc_notification(event);
rpc.notify("event", Some(event)).await?;
}
Ok(())
}

View File

@@ -1,6 +0,0 @@
node_modules
dist
test_dist
coverage
yarn.lock
package-lock.json

View File

@@ -1,3 +0,0 @@
coverage
dist
generated

View File

@@ -1 +0,0 @@
export * from "./src/lib.js";

View File

@@ -1,107 +0,0 @@
import { RawClient, RPC } from "./src/lib";
import { WebsocketTransport, Request } from "yerpc";
type DeltaEvent = { id: string; contextId: number; field1: any; field2: any };
var selectedAccount = 0;
window.addEventListener("DOMContentLoaded", (_event) => {
(window as any).selectDeltaAccount = (id: string) => {
selectedAccount = Number(id);
window.dispatchEvent(new Event("account-changed"));
};
run().catch((err) => console.error("run failed", err));
});
async function run() {
const $main = document.getElementById("main")!;
const $side = document.getElementById("side")!;
const $head = document.getElementById("header")!;
const transport = new WebsocketTransport("ws://localhost:20808/ws");
const client = new RawClient(transport);
(window as any).client = client;
transport.on("request", (request: Request) => {
const method = request.method;
if (method === "event") {
const params = request.params! as DeltaEvent;
onIncomingEvent(params, params.id);
}
});
window.addEventListener("account-changed", async (_event: Event) => {
await client.selectAccount(selectedAccount);
listChatsForSelectedAccount();
});
await Promise.all([loadAccountsInHeader(), listChatsForSelectedAccount()]);
async function loadAccountsInHeader() {
const accounts = await client.getAllAccounts();
for (const account of accounts) {
if (account.type === "Configured") {
write(
$head,
`<a href="#" onclick="selectDeltaAccount(${account.id})">
${account.addr!}
</a>&nbsp;`
);
}
}
}
async function listChatsForSelectedAccount() {
clear($main);
const selectedAccount = await client.getSelectedAccountId();
if (!selectedAccount) return write($main, "No account selected");
const info = await client.getAccountInfo(selectedAccount);
if (info.type !== "Configured") {
return write($main, "Account is not configured");
}
write($main, `<h2>${info.addr!}</h2>`);
const chats = await client.getChatlistEntries(
selectedAccount,
0,
null,
null
);
for (const [chatId, _messageId] of chats) {
const chat = await client.chatlistGetFullChatById(
selectedAccount,
chatId
);
write($main, `<h3>${chat.name}</h3>`);
const messageIds = await client.messageListGetMessageIds(
selectedAccount,
chatId,
0
);
const messages = await client.messageGetMessages(
selectedAccount,
messageIds
);
for (const [_messageId, message] of Object.entries(messages)) {
write($main, `<p>${message.text}</p>`);
}
}
}
function onIncomingEvent(event: DeltaEvent, name: string) {
write(
$side,
`
<p class="message">
[<strong>${name}</strong> on account ${event.contextId}]<br>
<em>f1:</em> ${JSON.stringify(event.field1)}<br>
<em>f2:</em> ${JSON.stringify(event.field2)}
</p>`
);
}
}
function write(el: HTMLElement, html: string) {
el.innerHTML += html;
}
function clear(el: HTMLElement) {
el.innerHTML = "";
}

View File

@@ -1,251 +0,0 @@
// AUTO-GENERATED by yerpc-derive
import * as T from "./types.js"
import * as RPC from "./jsonrpc.js"
type RequestMethod = (method: string, params?: RPC.Params) => Promise<unknown>;
type NotificationMethod = (method: string, params?: RPC.Params) => void;
interface Transport {
request: RequestMethod,
notification: NotificationMethod
}
export class RawClient {
constructor(private _transport: Transport) {}
/**
* Check if an email address is valid.
*/
public checkEmailValidity(email: string): Promise<boolean> {
return (this._transport.request('check_email_validity', [email] as RPC.Params)) as Promise<boolean>;
}
/**
* Get general system info.
*/
public getSystemInfo(): Promise<Record<string,string>> {
return (this._transport.request('get_system_info', [] as RPC.Params)) as Promise<Record<string,string>>;
}
public addAccount(): Promise<T.U32> {
return (this._transport.request('add_account', [] as RPC.Params)) as Promise<T.U32>;
}
public removeAccount(accountId: T.U32): Promise<null> {
return (this._transport.request('remove_account', [accountId] as RPC.Params)) as Promise<null>;
}
public getAllAccountIds(): Promise<(T.U32)[]> {
return (this._transport.request('get_all_account_ids', [] as RPC.Params)) as Promise<(T.U32)[]>;
}
/**
* Select account id for internally selected state.
* TODO: Likely this is deprecated as all methods take an account id now.
*/
public selectAccount(id: T.U32): Promise<null> {
return (this._transport.request('select_account', [id] as RPC.Params)) as Promise<null>;
}
/**
* Get the selected account id of the internal state..
* TODO: Likely this is deprecated as all methods take an account id now.
*/
public getSelectedAccountId(): Promise<(T.U32|null)> {
return (this._transport.request('get_selected_account_id', [] as RPC.Params)) as Promise<(T.U32|null)>;
}
/**
* Get a list of all configured accounts.
*/
public getAllAccounts(): Promise<(T.Account)[]> {
return (this._transport.request('get_all_accounts', [] as RPC.Params)) as Promise<(T.Account)[]>;
}
/**
* Get top-level info for an account.
*/
public getAccountInfo(accountId: T.U32): Promise<T.Account> {
return (this._transport.request('get_account_info', [accountId] as RPC.Params)) as Promise<T.Account>;
}
/**
* Returns provider for the given domain.
*
* This function looks up domain in offline database.
*
* For compatibility, email address can be passed to this function
* instead of the domain.
*/
public getProviderInfo(accountId: T.U32, email: string): Promise<(T.ProviderInfo|null)> {
return (this._transport.request('get_provider_info', [accountId, email] as RPC.Params)) as Promise<(T.ProviderInfo|null)>;
}
/**
* Checks if the context is already configured.
*/
public isConfigured(accountId: T.U32): Promise<boolean> {
return (this._transport.request('is_configured', [accountId] as RPC.Params)) as Promise<boolean>;
}
/**
* Get system info for an account.
*/
public getInfo(accountId: T.U32): Promise<Record<string,string>> {
return (this._transport.request('get_info', [accountId] as RPC.Params)) as Promise<Record<string,string>>;
}
public setConfig(accountId: T.U32, key: string, value: (string|null)): Promise<null> {
return (this._transport.request('set_config', [accountId, key, value] as RPC.Params)) as Promise<null>;
}
public batchSetConfig(accountId: T.U32, config: Record<string,(string|null)>): Promise<null> {
return (this._transport.request('batch_set_config', [accountId, config] as RPC.Params)) as Promise<null>;
}
public getConfig(accountId: T.U32, key: string): Promise<(string|null)> {
return (this._transport.request('get_config', [accountId, key] as RPC.Params)) as Promise<(string|null)>;
}
public batchGetConfig(accountId: T.U32, keys: (string)[]): Promise<Record<string,(string|null)>> {
return (this._transport.request('batch_get_config', [accountId, keys] as RPC.Params)) as Promise<Record<string,(string|null)>>;
}
/**
* Configures this account with the currently set parameters.
* Setup the credential config before calling this.
*/
public configure(accountId: T.U32): Promise<null> {
return (this._transport.request('configure', [accountId] as RPC.Params)) as Promise<null>;
}
/**
* Signal an ongoing process to stop.
*/
public stopOngoingProcess(accountId: T.U32): Promise<null> {
return (this._transport.request('stop_ongoing_process', [accountId] as RPC.Params)) as Promise<null>;
}
public autocryptInitiateKeyTransfer(accountId: T.U32): Promise<string> {
return (this._transport.request('autocrypt_initiate_key_transfer', [accountId] as RPC.Params)) as Promise<string>;
}
public autocryptContinueKeyTransfer(accountId: T.U32, messageId: T.U32, setupCode: string): Promise<null> {
return (this._transport.request('autocrypt_continue_key_transfer', [accountId, messageId, setupCode] as RPC.Params)) as Promise<null>;
}
public getChatlistEntries(accountId: T.U32, listFlags: (T.U32|null), queryString: (string|null), queryContactId: (T.U32|null)): Promise<(T.ChatListEntry)[]> {
return (this._transport.request('get_chatlist_entries', [accountId, listFlags, queryString, queryContactId] as RPC.Params)) as Promise<(T.ChatListEntry)[]>;
}
public getChatlistItemsByEntries(accountId: T.U32, entries: (T.ChatListEntry)[]): Promise<Record<T.U32,T.ChatListItemFetchResult>> {
return (this._transport.request('get_chatlist_items_by_entries', [accountId, entries] as RPC.Params)) as Promise<Record<T.U32,T.ChatListItemFetchResult>>;
}
public chatlistGetFullChatById(accountId: T.U32, chatId: T.U32): Promise<T.FullChat> {
return (this._transport.request('chatlist_get_full_chat_by_id', [accountId, chatId] as RPC.Params)) as Promise<T.FullChat>;
}
public acceptChat(accountId: T.U32, chatId: T.U32): Promise<null> {
return (this._transport.request('accept_chat', [accountId, chatId] as RPC.Params)) as Promise<null>;
}
public blockChat(accountId: T.U32, chatId: T.U32): Promise<null> {
return (this._transport.request('block_chat', [accountId, chatId] as RPC.Params)) as Promise<null>;
}
public messageListGetMessageIds(accountId: T.U32, chatId: T.U32, flags: T.U32): Promise<(T.U32)[]> {
return (this._transport.request('message_list_get_message_ids', [accountId, chatId, flags] as RPC.Params)) as Promise<(T.U32)[]>;
}
public messageGetMessage(accountId: T.U32, messageId: T.U32): Promise<T.Message> {
return (this._transport.request('message_get_message', [accountId, messageId] as RPC.Params)) as Promise<T.Message>;
}
public messageGetMessages(accountId: T.U32, messageIds: (T.U32)[]): Promise<Record<T.U32,T.Message>> {
return (this._transport.request('message_get_messages', [accountId, messageIds] as RPC.Params)) as Promise<Record<T.U32,T.Message>>;
}
/**
* Get a single contact options by ID.
*/
public contactsGetContact(accountId: T.U32, contactId: T.U32): Promise<T.Contact> {
return (this._transport.request('contacts_get_contact', [accountId, contactId] as RPC.Params)) as Promise<T.Contact>;
}
/**
* Add a single contact as a result of an explicit user action.
*
* Returns contact id of the created or existing contact
*/
public contactsCreateContact(accountId: T.U32, email: string, name: (string|null)): Promise<T.U32> {
return (this._transport.request('contacts_create_contact', [accountId, email, name] as RPC.Params)) as Promise<T.U32>;
}
/**
* Returns contact id of the created or existing DM chat with that contact
*/
public contactsCreateChatByContactId(accountId: T.U32, contactId: T.U32): Promise<T.U32> {
return (this._transport.request('contacts_create_chat_by_contact_id', [accountId, contactId] as RPC.Params)) as Promise<T.U32>;
}
public contactsBlock(accountId: T.U32, contactId: T.U32): Promise<null> {
return (this._transport.request('contacts_block', [accountId, contactId] as RPC.Params)) as Promise<null>;
}
public contactsUnblock(accountId: T.U32, contactId: T.U32): Promise<null> {
return (this._transport.request('contacts_unblock', [accountId, contactId] as RPC.Params)) as Promise<null>;
}
public contactsGetBlocked(accountId: T.U32): Promise<(T.Contact)[]> {
return (this._transport.request('contacts_get_blocked', [accountId] as RPC.Params)) as Promise<(T.Contact)[]>;
}
public contactsGetContactIds(accountId: T.U32, listFlags: T.U32, query: (string|null)): Promise<(T.U32)[]> {
return (this._transport.request('contacts_get_contact_ids', [accountId, listFlags, query] as RPC.Params)) as Promise<(T.U32)[]>;
}
/**
* Get a list of contacts.
* (formerly called getContacts2 in desktop)
*/
public contactsGetContacts(accountId: T.U32, listFlags: T.U32, query: (string|null)): Promise<(T.Contact)[]> {
return (this._transport.request('contacts_get_contacts', [accountId, listFlags, query] as RPC.Params)) as Promise<(T.Contact)[]>;
}
public contactsGetContactsByIds(accountId: T.U32, ids: (T.U32)[]): Promise<Record<T.U32,T.Contact>> {
return (this._transport.request('contacts_get_contacts_by_ids', [accountId, ids] as RPC.Params)) as Promise<Record<T.U32,T.Contact>>;
}
/**
* Returns the messageid of the sent message
*/
public miscSendTextMessage(accountId: T.U32, text: string, chatId: T.U32): Promise<T.U32> {
return (this._transport.request('misc_send_text_message', [accountId, text, chatId] as RPC.Params)) as Promise<T.U32>;
}
}

View File

@@ -1,3 +0,0 @@
// AUTO-GENERATED by typescript-type-def
export type EventTypeName=("Info"|"SmtpConnected"|"ImapConnected"|"SmtpMessageSent"|"ImapMessageDeleted"|"ImapMessageMoved"|"NewBlobFile"|"DeletedBlobFile"|"Warning"|"Error"|"ErrorSelfNotInGroup"|"MsgsChanged"|"IncomingMsg"|"MsgsNoticed"|"MsgDelivered"|"MsgFailed"|"MsgRead"|"ChatModified"|"ChatEphemeralTimerModified"|"ContactsChanged"|"LocationChanged"|"ConfigureProgress"|"ImexProgress"|"ImexFileWritten"|"SecurejoinInviterProgress"|"SecurejoinJoinerProgress"|"ConnectivityChanged"|"SelfavatarChanged"|"WebxdcStatusUpdate");

View File

@@ -1,10 +0,0 @@
// AUTO-GENERATED by typescript-type-def
export type JSONValue=(null|boolean|number|string|(JSONValue)[]|{[key:string]:JSONValue;});
export type Params=((JSONValue)[]|Record<string,JSONValue>);
export type U32=number;
export type Request={"jsonrpc":"2.0";"method":string;"params"?:Params;"id"?:U32;};
export type I32=number;
export type Error={"code":I32;"message":string;"data"?:JSONValue;};
export type Response={"jsonrpc":"2.0";"id":(U32|null);"result"?:JSONValue;"error"?:Error;};
export type Message=(Request|Response);

View File

@@ -1,54 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<style>
body {
font-family: monospace;
background: black;
color: grey;
}
.grid {
display: grid;
grid-template-columns: 3fr 1fr;
grid-template-areas: "a a" "b c";
}
.message {
color: red;
}
#header {
grid-area: a;
color: white;
font-size: 1.2rem;
}
#header a {
color: white;
font-weight: bold;
}
#main {
grid-area: b;
color: green;
}
#main h2,
#main h3 {
color: blue;
}
#side {
grid-area: c;
color: #777;
overflow-y: auto;
}
</style>
<script type="module" src="dist/example.bundle.js"></script>
</head>
<body>
<div class="grid">
<div id="header"></div>
<div id="main"></div>
<div id="side"><h2>log</h2></div>
</div>
<p>
Tip: open the dev console and use the client with
<code>window.client</code>
</p>
</body>
</html>

View File

@@ -1,13 +0,0 @@
import { Deltachat } from "./dist/deltachat.js";
run().catch(console.error);
async function run() {
const delta = new Deltachat();
delta.addEventListener("event", (event) => {
console.log("event", event.data);
});
const accounts = await delta.rpc.getAllAccounts();
console.log("accounts", accounts);
}

View File

@@ -1,41 +0,0 @@
{
"name": "@deltachat/jsonrpc-client",
"version": "0.1.0",
"main": "dist/deltachat.js",
"types": "dist/deltachat.d.ts",
"type": "module",
"author": "Delta Chat Developers (ML) <delta@codespeak.net>",
"license": "MPL-2.0",
"scripts": {
"prettier:check": "prettier --check **.ts",
"prettier:fix": "prettier --write **.ts",
"build": "npm run generate-bindings && tsc",
"bundle": "npm run build && esbuild --bundle dist/deltachat.js --outfile=dist/deltachat.bundle.js",
"generate-bindings": "cargo test",
"example:build": "tsc && esbuild --bundle dist/example.js --outfile=dist/example.bundle.js",
"example:dev": "esbuild example.ts --bundle --outdir=dist --servedir=.",
"coverage": "tsc -b test && COVERAGE=1 NODE_OPTIONS=--enable-source-maps c8 --include \"dist/*\" -r text -r html -r json mocha test_dist && node report_api_coverage.mjs",
"test": "rm -rf dist && npm run build && npm run coverage && npm run prettier:check"
},
"dependencies": {
"isomorphic-ws": "^4.0.1",
"tiny-emitter": "git+https://github.com/Simon-Laux/tiny-emitter.git",
"yerpc": "^0.2.3"
},
"devDependencies": {
"prettier": "^2.6.2",
"chai-as-promised": "^7.1.1",
"@types/chai": "^4.2.21",
"@types/chai-as-promised": "^7.1.5",
"@types/mocha": "^9.0.0",
"@types/node-fetch": "^2.5.7",
"@types/ws": "^7.2.4",
"c8": "^7.10.0",
"chai": "^4.3.4",
"esbuild": "^0.14.11",
"mocha": "^9.1.1",
"node-fetch": "^2.6.1",
"typescript": "^4.5.5",
"ws": "^8.5.0"
}
}

View File

@@ -1,28 +0,0 @@
import { readFileSync } from "fs";
// only checks for the coverge of the api functions in bindings.ts for now
const generated_file = "typescript/generated/client.ts";
const json = JSON.parse(readFileSync("./coverage/coverage-final.json"));
const jsonCoverage =
json[Object.keys(json).find((k) => k.includes(generated_file))];
const fnMap = Object.keys(jsonCoverage.fnMap).map(
(key) => jsonCoverage.fnMap[key]
);
const htmlCoverage = readFileSync(
"./coverage/" + generated_file + ".html",
"utf8"
);
const uncoveredLines = htmlCoverage
.split("\n")
.filter((line) => line.includes(`"function not covered"`));
const uncoveredFunctions = uncoveredLines.map(
(line) => />([\w_]+)\(/.exec(line)[1]
);
console.log(
"\nUncovered api functions:\n" +
uncoveredFunctions
.map((uF) => fnMap.find(({ name }) => name === uF))
.map(
({ name, line }) => `.${name.padEnd(40)} (${generated_file}:${line})`
)
.join("\n")
);

View File

@@ -1,82 +0,0 @@
import * as T from "../generated/types.js";
import * as RPC from "../generated/jsonrpc.js";
import { RawClient } from "../generated/client.js";
import { EventTypeName } from "../generated/events.js";
import { WebsocketTransport, BaseTransport, Request } from "yerpc";
import { TinyEmitter } from "tiny-emitter";
export type DeltachatEvent = {
id: EventTypeName;
contextId: number;
field1: any;
field2: any;
};
export type Events = Record<
EventTypeName | "ALL",
(event: DeltachatEvent) => void
>;
export class BaseDeltachat<
Transport extends BaseTransport
> extends TinyEmitter<Events> {
rpc: RawClient;
account?: T.Account;
constructor(protected transport: Transport) {
super();
this.rpc = new RawClient(this.transport);
this.transport.on("request", (request: Request) => {
const method = request.method;
if (method === "event") {
const event = request.params! as DeltachatEvent;
this.emit(event.id, event);
this.emit("ALL", event);
if (this.contextEmitters[event.contextId]) {
this.contextEmitters[event.contextId].emit(event.id, event);
this.contextEmitters[event.contextId].emit("ALL", event);
}
}
});
}
async selectAccount(id: number) {
await this.rpc.selectAccount(id);
this.account = await this.rpc.getAccountInfo(id);
}
async listAccounts(): Promise<T.Account[]> {
return await this.rpc.getAllAccounts();
}
private contextEmitters: TinyEmitter<Events>[] = [];
getContextEvents(account_id: number) {
if (this.contextEmitters[account_id]) {
return this.contextEmitters[account_id];
} else {
this.contextEmitters[account_id] = new TinyEmitter();
return this.contextEmitters[account_id];
}
}
}
export type Opts = {
url: string;
};
export const DEFAULT_OPTS: Opts = {
url: "ws://localhost:20808/ws",
};
export class Deltachat extends BaseDeltachat<WebsocketTransport> {
opts: Opts;
close() {
this.transport._socket.close();
}
constructor(opts: Opts | string | undefined) {
if (typeof opts === "string") opts = { url: opts };
if (opts) opts = { ...DEFAULT_OPTS, ...opts };
else opts = { ...DEFAULT_OPTS };
super(new WebsocketTransport(opts.url));
this.opts = opts;
}
}

View File

@@ -1,6 +0,0 @@
export * as RPC from "../generated/jsonrpc.js";
export * as T from "../generated/types.js";
export * from "../generated/events.js";
export { RawClient } from "../generated/client.js";
export * from "./client.js";
export * as yerpc from "yerpc";

View File

@@ -1 +0,0 @@
# tests need to be ported to new API

View File

@@ -1,158 +0,0 @@
import { strictEqual } from "assert";
import chai, { assert, expect } from "chai";
import chaiAsPromised from "chai-as-promised";
chai.use(chaiAsPromised);
import { Deltachat } from "../dist/deltachat.js";
import {
CMD_API_Server_Handle,
CMD_API_SERVER_PORT,
startCMD_API_Server,
} from "./test_base.js";
describe("basic tests", () => {
let server_handle: CMD_API_Server_Handle;
let dc: Deltachat;
before(async () => {
server_handle = await startCMD_API_Server(CMD_API_SERVER_PORT);
// make sure server is up by the time we continue
await new Promise((res) => setTimeout(res, 100));
dc = new Deltachat({
url: "ws://localhost:" + CMD_API_SERVER_PORT + "/ws",
});
dc.on("ALL", (event) => {
//console.log("event", event);
});
});
after(async () => {
dc && dc.close();
await server_handle.close();
});
it("check email", async () => {
const positive_test_cases = [
"email@example.com",
"36aa165ae3406424e0c61af17700f397cad3fe8ab83d682d0bddf3338a5dd52e@yggmail@yggmail",
];
const negative_test_cases = ["email@", "example.com", "emai221"];
expect(
await Promise.all(
positive_test_cases.map((email) => dc.rpc.checkEmailValidity(email))
)
).to.not.contain(false);
expect(
await Promise.all(
negative_test_cases.map((email) => dc.rpc.checkEmailValidity(email))
)
).to.not.contain(true);
});
it("system info", async () => {
const system_info = await dc.rpc.getSystemInfo();
expect(system_info).to.contain.keys([
"arch",
"num_cpus",
"deltachat_core_version",
"sqlite_version",
]);
});
describe("account managment", () => {
it("should create account", async () => {
await dc.rpc.addAccount();
assert((await dc.rpc.getAllAccountIds()).length === 1);
});
it("should remove the account again", async () => {
await dc.rpc.removeAccount((await dc.rpc.getAllAccountIds())[0]);
assert((await dc.rpc.getAllAccountIds()).length === 0);
});
it("should create multiple accounts", async () => {
await dc.rpc.addAccount();
await dc.rpc.addAccount();
await dc.rpc.addAccount();
await dc.rpc.addAccount();
assert((await dc.rpc.getAllAccountIds()).length === 4);
});
});
describe("contact managment", function () {
let acc: number;
before(async () => {
acc = await dc.rpc.addAccount();
});
it("block and unblock contact", async function () {
const contactId = await dc.rpc.contactsCreateContact(
acc,
"example@delta.chat",
null
);
expect((await dc.rpc.contactsGetContact(acc, contactId)).is_blocked).to.be
.false;
await dc.rpc.contactsBlock(acc, contactId);
expect((await dc.rpc.contactsGetContact(acc, contactId)).is_blocked).to.be
.true;
expect(await dc.rpc.contactsGetBlocked(acc)).to.have.length(1);
await dc.rpc.contactsUnblock(acc, contactId);
expect((await dc.rpc.contactsGetContact(acc, contactId)).is_blocked).to.be
.false;
expect(await dc.rpc.contactsGetBlocked(acc)).to.have.length(0);
});
});
describe("configuration", function () {
let acc: number;
before(async () => {
acc = await dc.rpc.addAccount();
});
it("set and retrive", async function () {
await dc.rpc.setConfig(acc, "addr", "valid@email");
assert((await dc.rpc.getConfig(acc, "addr")) == "valid@email");
});
it("set invalid key should throw", async function () {
await expect(dc.rpc.setConfig(acc, "invalid_key", "some value")).to.be
.eventually.rejected;
});
it("get invalid key should throw", async function () {
await expect(dc.rpc.getConfig(acc, "invalid_key")).to.be.eventually
.rejected;
});
it("set and retrive ui.*", async function () {
await dc.rpc.setConfig(acc, "ui.chat_bg", "color:red");
assert((await dc.rpc.getConfig(acc, "ui.chat_bg")) == "color:red");
});
it("set and retrive (batch)", async function () {
const config = { addr: "valid@email", mail_pw: "1234" };
await dc.rpc.batchSetConfig(acc, config);
const retrieved = await dc.rpc.batchGetConfig(acc, Object.keys(config));
expect(retrieved).to.deep.equal(config);
});
it("set and retrive ui.* (batch)", async function () {
const config = {
"ui.chat_bg": "color:green",
"ui.enter_key_sends": "true",
};
await dc.rpc.batchSetConfig(acc, config);
const retrieved = await dc.rpc.batchGetConfig(acc, Object.keys(config));
expect(retrieved).to.deep.equal(config);
});
it("set and retrive mixed(ui and core) (batch)", async function () {
const config = {
"ui.chat_bg": "color:yellow",
"ui.enter_key_sends": "false",
addr: "valid2@email",
mail_pw: "123456",
};
await dc.rpc.batchSetConfig(acc, config);
const retrieved = await dc.rpc.batchGetConfig(acc, Object.keys(config));
expect(retrieved).to.deep.equal(config);
});
});
});

View File

@@ -1,203 +0,0 @@
import { assert, expect } from "chai";
import { Deltachat, DeltachatEvent, EventTypeName } from "../dist/deltachat.js";
import {
CMD_API_Server_Handle,
CMD_API_SERVER_PORT,
createTempUser,
startCMD_API_Server,
} from "./test_base.js";
describe("online tests", function () {
let server_handle: CMD_API_Server_Handle;
let dc: Deltachat;
let account: { email: string; password: string };
let account2: { email: string; password: string };
let acc1: number, acc2: number;
before(async function () {
this.timeout(12000)
if (!process.env.DCC_NEW_TMP_EMAIL) {
if (process.env.COVERAGE && !process.env.COVERAGE_OFFLINE) {
console.error(
"CAN NOT RUN COVERAGE correctly: Missing DCC_NEW_TMP_EMAIL environment variable!\n\n",
"You can set COVERAGE_OFFLINE=1 to circumvent this check and skip the online tests, but those coverage results will be wrong, because some functions can only be tested in the online test"
);
process.exit(1);
}
console.log(
"Missing DCC_NEW_TMP_EMAIL environment variable!, skip intergration tests"
);
this.skip();
}
server_handle = await startCMD_API_Server(CMD_API_SERVER_PORT);
dc = new Deltachat({
url: "ws://localhost:" + CMD_API_SERVER_PORT + "/ws",
});
dc.on("ALL", ({ id, contextId }) => {
if (id !== "Info") console.log(contextId, id);
});
account = await createTempUser(process.env.DCC_NEW_TMP_EMAIL);
if (!account || !account.email || !account.password) {
console.log(
"We didn't got back an account from the api, skip intergration tests"
);
this.skip();
}
account2 = await createTempUser(process.env.DCC_NEW_TMP_EMAIL);
if (!account2 || !account2.email || !account2.password) {
console.log(
"We didn't got back an account2 from the api, skip intergration tests"
);
this.skip();
}
});
after(async () => {
dc && dc.close();
server_handle && (await server_handle.close());
});
let are_configured = false;
it("configure test accounts", async function () {
this.timeout(20000);
acc1 = await dc.rpc.addAccount();
await dc.rpc.setConfig(acc1, "addr", account.email);
await dc.rpc.setConfig(acc1, "mail_pw", account.password);
let configure_promise = dc.rpc.configure(acc1);
acc2 = await dc.rpc.addAccount();
await dc.rpc.batchSetConfig(acc2, {
addr: account2.email,
mail_pw: account2.password,
});
await Promise.all([configure_promise, dc.rpc.configure(acc2)]);
are_configured = true;
});
it("send and recieve text message", async function () {
if (!are_configured) {
this.skip();
}
this.timeout(15000);
const contactId = await dc.rpc.contactsCreateContact(
acc1,
account2.email,
null
);
const chatId = await dc.rpc.contactsCreateChatByContactId(acc1, contactId);
const eventPromise = Promise.race([
waitForEvent(dc, "MsgsChanged", acc2),
waitForEvent(dc, "IncomingMsg", acc2),
]);
dc.rpc.miscSendTextMessage(acc1, "Hello", chatId);
const { field1: chatIdOnAccountB } = await eventPromise;
await dc.rpc.acceptChat(acc2, chatIdOnAccountB);
const messageList = await dc.rpc.messageListGetMessageIds(
acc2,
chatIdOnAccountB,
0
);
expect(messageList).have.length(1);
const message = await dc.rpc.messageGetMessage(acc2, messageList[0]);
expect(message.text).equal("Hello");
});
it("send and recieve text message roundtrip, encrypted on answer onwards", async function () {
if (!are_configured) {
this.skip();
}
this.timeout(10000);
// send message from A to B
const contactId = await dc.rpc.contactsCreateContact(
acc1,
account2.email,
null
);
const chatId = await dc.rpc.contactsCreateChatByContactId(acc1, contactId);
const eventPromise = Promise.race([
waitForEvent(dc, "MsgsChanged", acc2),
waitForEvent(dc, "IncomingMsg", acc2),
]);
dc.rpc.miscSendTextMessage(acc1, "Hello2", chatId);
// wait for message from A
console.log("wait for message from A");
const event = await eventPromise;
const { field1: chatIdOnAccountB } = event;
await dc.rpc.acceptChat(acc2, chatIdOnAccountB);
const messageList = await dc.rpc.messageListGetMessageIds(
acc2,
chatIdOnAccountB,
0
);
const message = await dc.rpc.messageGetMessage(
acc2,
messageList.reverse()[0]
);
expect(message.text).equal("Hello2");
// Send message back from B to A
const eventPromise2 = Promise.race([
waitForEvent(dc, "MsgsChanged", acc1),
waitForEvent(dc, "IncomingMsg", acc1),
]);
dc.rpc.miscSendTextMessage(acc2, "super secret message", chatId);
// Check if answer arives at A and if it is encrypted
await eventPromise2;
const messageId = (
await dc.rpc.messageListGetMessageIds(acc1, chatId, 0)
).reverse()[0];
const message2 = await dc.rpc.messageGetMessage(acc1, messageId);
expect(message2.text).equal("super secret message");
expect(message2.show_padlock).equal(true);
});
it("get provider info for example.com", async () => {
const acc = await dc.rpc.addAccount();
const info = await dc.rpc.getProviderInfo(acc, "example.com");
expect(info).to.be.not.null;
expect(info?.overview_page).to.equal(
"https://providers.delta.chat/example-com"
);
expect(info?.status).to.equal(3);
});
it("get provider info - domain and email should give same result", async () => {
const acc = await dc.rpc.addAccount();
const info_domain = await dc.rpc.getProviderInfo(acc, "example.com");
const info_email = await dc.rpc.getProviderInfo(acc, "hi@example.com");
expect(info_email).to.deep.equal(info_domain);
});
});
type event_data = {
contextId: number;
id: EventTypeName;
[key: string]: any;
};
async function waitForEvent(
dc: Deltachat,
event: EventTypeName,
accountId: number
): Promise<event_data> {
return new Promise((res, rej) => {
const callback = (ev: DeltachatEvent) => {
if (ev.contextId == accountId) {
dc.off(event, callback);
res(ev);
}
};
dc.on(event, callback);
});
}

View File

@@ -1,95 +0,0 @@
import { tmpdir } from "os";
import { join } from "path";
import { mkdtemp, rm } from "fs/promises";
import { existsSync } from "fs";
import { spawn, exec } from "child_process";
import { unwrapPromise } from "./ts_helpers.js";
import fetch from "node-fetch";
/* port is not configurable yet */
function getTargetDir(): Promise<string> {
return new Promise((res, rej) => {
exec(
"cargo metadata --no-deps --format-version 1",
(error, stdout, stderr) => {
if (error) {
console.log("error", error);
rej(error);
} else {
try {
const json = JSON.parse(stdout);
res(json.target_directory);
} catch (error) {
console.log("json error", error);
rej(error);
}
}
}
);
});
}
export const CMD_API_SERVER_PORT = 20808;
export async function startCMD_API_Server(port: typeof CMD_API_SERVER_PORT) {
const tmp_dir = await mkdtemp(join(tmpdir(), "test_prefix"));
const path_of_server = join(await getTargetDir(), "debug/webserver");
console.log(path_of_server);
if (!existsSync(path_of_server)) {
throw new Error(
"server executable does not exist, you need to build it first" +
"\nserver executable not found at " +
path_of_server
);
}
const server = spawn(path_of_server, {
cwd: tmp_dir,
env: {
RUST_LOG: "info",
},
});
let should_close = false;
server.on("exit", () => {
if (should_close) {
return;
}
throw new Error("Server quit");
});
server.stderr.pipe(process.stderr);
//server.stdout.pipe(process.stdout)
return {
close: async () => {
should_close = true;
if (!server.kill(9)) {
console.log("server termination failed");
}
await rm(tmp_dir, { recursive: true });
},
};
}
export type CMD_API_Server_Handle = unwrapPromise<
ReturnType<typeof startCMD_API_Server>
>;
export async function createTempUser(url: string) {
async function postData(url = "") {
// Default options are marked with *
const response = await fetch(url, {
method: "POST", // *GET, POST, PUT, DELETE, etc.
headers: {
"cache-control": "no-cache",
},
});
return response.json(); // parses JSON response into native JavaScript objects
}
return await postData(url);
}

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