mirror of
https://github.com/chatmail/core.git
synced 2026-04-13 11:40:41 +03:00
Compare commits
12 Commits
ci/python-
...
mergetry1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ad4be80b4e | ||
|
|
8737c1d142 | ||
|
|
964fe466cc | ||
|
|
43936e7db7 | ||
|
|
0e80ce9c39 | ||
|
|
c652bae68a | ||
|
|
bc904a495d | ||
|
|
8d99444c6a | ||
|
|
9dab53e0af | ||
|
|
360089ac74 | ||
|
|
e892c5cf4d | ||
|
|
9ad4c9a6fe |
@@ -1,4 +1,5 @@
|
|||||||
version: 2.1
|
version: 2.1
|
||||||
|
|
||||||
executors:
|
executors:
|
||||||
default:
|
default:
|
||||||
docker:
|
docker:
|
||||||
@@ -12,7 +13,7 @@ restore-workspace: &restore-workspace
|
|||||||
restore-cache: &restore-cache
|
restore-cache: &restore-cache
|
||||||
restore_cache:
|
restore_cache:
|
||||||
keys:
|
keys:
|
||||||
- cargo-v2-{{ checksum "rust-toolchain" }}-{{ checksum "Cargo.toml" }}-{{ checksum "Cargo.lock" }}-{{ arch }}
|
- cargo-v0-{{ checksum "rust-toolchain" }}-{{ checksum "Cargo.toml" }}-{{ checksum "Cargo.lock" }}-{{ arch }}
|
||||||
- repo-source-{{ .Branch }}-{{ .Revision }}
|
- repo-source-{{ .Branch }}-{{ .Revision }}
|
||||||
|
|
||||||
commands:
|
commands:
|
||||||
@@ -23,9 +24,20 @@ commands:
|
|||||||
steps:
|
steps:
|
||||||
- *restore-workspace
|
- *restore-workspace
|
||||||
- *restore-cache
|
- *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:
|
- run:
|
||||||
name: Test (<< parameters.target >>)
|
name: Test (<< parameters.target >>)
|
||||||
command: TARGET=<< parameters.target >> ci_scripts/run-rust-test.sh
|
command: TARGET=<< parameters.target >> ci/run.sh
|
||||||
no_output_timeout: 15m
|
no_output_timeout: 15m
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
@@ -41,23 +53,21 @@ jobs:
|
|||||||
command: cargo generate-lockfile
|
command: cargo generate-lockfile
|
||||||
- restore_cache:
|
- restore_cache:
|
||||||
keys:
|
keys:
|
||||||
- cargo-v2-{{ checksum "rust-toolchain" }}-{{ checksum "Cargo.toml" }}-{{ checksum "Cargo.lock" }}-{{ arch }}
|
- cargo-v0-{{ checksum "rust-toolchain" }}-{{ checksum "Cargo.toml" }}-{{ checksum "Cargo.lock" }}-{{ arch }}
|
||||||
- run: rustup install $(cat rust-toolchain)
|
- run: rustup install $(cat rust-toolchain)
|
||||||
- run: rustup default $(cat rust-toolchain)
|
- run: rustup default $(cat rust-toolchain)
|
||||||
- run: rustup component add --toolchain $(cat rust-toolchain) rustfmt
|
- run: rustup component add --toolchain $(cat rust-toolchain) rustfmt
|
||||||
- run: rustup component add --toolchain $(cat rust-toolchain) clippy-preview
|
|
||||||
- run: cargo update
|
- run: cargo update
|
||||||
- run: cargo fetch
|
- run: cargo fetch
|
||||||
- run: rustc +stable --version
|
- run: rustc +stable --version
|
||||||
- run: rustc +$(cat rust-toolchain) --version
|
- run: rustc +$(cat rust-toolchain) --version
|
||||||
# make sure this git repo doesn't grow too big
|
- run: rm -rf .git
|
||||||
- run: git gc
|
|
||||||
- persist_to_workspace:
|
- persist_to_workspace:
|
||||||
root: /mnt
|
root: /mnt
|
||||||
paths:
|
paths:
|
||||||
- crate
|
- crate
|
||||||
- save_cache:
|
- save_cache:
|
||||||
key: cargo-v2-{{ checksum "rust-toolchain" }}-{{ checksum "Cargo.toml" }}-{{ checksum "Cargo.lock" }}-{{ arch }}
|
key: cargo-v0-{{ checksum "rust-toolchain" }}-{{ checksum "Cargo.toml" }}-{{ checksum "Cargo.lock" }}-{{ arch }}
|
||||||
paths:
|
paths:
|
||||||
- "~/.cargo"
|
- "~/.cargo"
|
||||||
- "~/.rustup"
|
- "~/.rustup"
|
||||||
@@ -92,7 +102,7 @@ jobs:
|
|||||||
- run: cargo fetch
|
- run: cargo fetch
|
||||||
- run:
|
- run:
|
||||||
name: Test
|
name: Test
|
||||||
command: TARGET=x86_64-apple-darwin ci_scripts/run-rust-test.sh
|
command: TARGET=x86_64-apple-darwin ci/run.sh
|
||||||
|
|
||||||
test_x86_64-unknown-linux-gnu:
|
test_x86_64-unknown-linux-gnu:
|
||||||
executor: default
|
executor: default
|
||||||
@@ -114,18 +124,18 @@ jobs:
|
|||||||
|
|
||||||
|
|
||||||
build_test_docs_wheel:
|
build_test_docs_wheel:
|
||||||
docker:
|
machine: True
|
||||||
- image: deltachat/coredeps
|
|
||||||
environment:
|
|
||||||
TESTS: 1
|
|
||||||
DOCS: 1
|
|
||||||
working_directory: /mnt/crate
|
|
||||||
steps:
|
steps:
|
||||||
- *restore-workspace
|
- checkout
|
||||||
- *restore-cache
|
# - run: docker pull deltachat/doxygen
|
||||||
|
- run: docker pull deltachat/coredeps
|
||||||
- run:
|
- run:
|
||||||
name: build docs, run tests and build wheels
|
name: build docs, run tests and build wheels
|
||||||
command: ci_scripts/run-python.sh
|
command: ci_scripts/ci_run.sh
|
||||||
|
environment:
|
||||||
|
TESTS: 1
|
||||||
|
DOCS: 1
|
||||||
|
|
||||||
- run:
|
- run:
|
||||||
name: copying docs and wheels to workspace
|
name: copying docs and wheels to workspace
|
||||||
command: |
|
command: |
|
||||||
@@ -133,6 +143,7 @@ jobs:
|
|||||||
# cp -av docs workspace/c-docs
|
# cp -av docs workspace/c-docs
|
||||||
cp -av python/.docker-tox/wheelhouse workspace/
|
cp -av python/.docker-tox/wheelhouse workspace/
|
||||||
cp -av python/doc/_build/ workspace/py-docs
|
cp -av python/doc/_build/ workspace/py-docs
|
||||||
|
|
||||||
- persist_to_workspace:
|
- persist_to_workspace:
|
||||||
root: workspace
|
root: workspace
|
||||||
paths:
|
paths:
|
||||||
@@ -141,55 +152,35 @@ jobs:
|
|||||||
- wheelhouse
|
- wheelhouse
|
||||||
|
|
||||||
upload_docs_wheels:
|
upload_docs_wheels:
|
||||||
machine: true
|
machine: True
|
||||||
steps:
|
steps:
|
||||||
- checkout
|
- checkout
|
||||||
- attach_workspace:
|
- attach_workspace:
|
||||||
at: workspace
|
at: workspace
|
||||||
- run: pyenv global 3.5.2
|
|
||||||
- run: ls -laR workspace
|
- run: ls -laR workspace
|
||||||
- run: ci_scripts/ci_upload.sh workspace/py-docs workspace/wheelhouse
|
- run: ci_scripts/ci_upload.sh workspace/py-docs workspace/wheelhouse
|
||||||
|
|
||||||
clippy:
|
|
||||||
executor: default
|
|
||||||
steps:
|
|
||||||
- *restore-workspace
|
|
||||||
- *restore-cache
|
|
||||||
- run:
|
|
||||||
name: Run cargo clippy
|
|
||||||
command: cargo clippy --all
|
|
||||||
|
|
||||||
|
|
||||||
workflows:
|
workflows:
|
||||||
version: 2.1
|
version: 2.1
|
||||||
|
|
||||||
test:
|
test:
|
||||||
jobs:
|
jobs:
|
||||||
- cargo_fetch
|
- build_test_docs_wheel
|
||||||
- build_test_docs_wheel:
|
|
||||||
requires:
|
|
||||||
- cargo_fetch
|
|
||||||
- upload_docs_wheels:
|
- upload_docs_wheels:
|
||||||
requires:
|
requires:
|
||||||
- build_test_docs_wheel
|
- build_test_docs_wheel
|
||||||
|
- cargo_fetch
|
||||||
- rustfmt:
|
- rustfmt:
|
||||||
requires:
|
requires:
|
||||||
- cargo_fetch
|
- cargo_fetch
|
||||||
- clippy:
|
|
||||||
requires:
|
|
||||||
- cargo_fetch
|
|
||||||
|
|
||||||
# Linux Desktop 64bit
|
# Linux Desktop
|
||||||
- test_x86_64-unknown-linux-gnu:
|
- test_x86_64-unknown-linux-gnu:
|
||||||
requires:
|
requires:
|
||||||
- cargo_fetch
|
- cargo_fetch
|
||||||
|
|
||||||
# Linux Desktop 32bit
|
# Linux Desktop
|
||||||
# - test_i686-unknown-linux-gnu:
|
|
||||||
# requires:
|
|
||||||
# - cargo_fetch
|
|
||||||
|
|
||||||
# Android 64bit
|
|
||||||
# - test_aarch64-linux-android:
|
# - test_aarch64-linux-android:
|
||||||
# requires:
|
# requires:
|
||||||
# - cargo_fetch
|
# - cargo_fetch
|
||||||
|
|||||||
6
.gitattributes
vendored
6
.gitattributes
vendored
@@ -2,11 +2,7 @@
|
|||||||
# 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
|
# binary files should be detected by git, however, to be sure, you can add them here explictly
|
||||||
# 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
|
|
||||||
*.png binary
|
*.png binary
|
||||||
*.jpg binary
|
*.jpg binary
|
||||||
*.gif binary
|
*.gif binary
|
||||||
|
|||||||
7
.gitignore
vendored
7
.gitignore
vendored
@@ -1,5 +1,6 @@
|
|||||||
/target
|
/target
|
||||||
**/*.rs.bk
|
**/*.rs.bk
|
||||||
|
Cargo.lock
|
||||||
|
|
||||||
# ignore vi temporaries
|
# ignore vi temporaries
|
||||||
*~
|
*~
|
||||||
@@ -16,9 +17,3 @@ python/.tox
|
|||||||
*.egg-info
|
*.egg-info
|
||||||
__pycache__
|
__pycache__
|
||||||
python/src/deltachat/capi*.so
|
python/src/deltachat/capi*.so
|
||||||
|
|
||||||
python/liveconfig*
|
|
||||||
|
|
||||||
# ignore doxgen generated files
|
|
||||||
deltachat-ffi/html
|
|
||||||
deltachat-ffi/xml
|
|
||||||
|
|||||||
3239
Cargo.lock
generated
3239
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
36
Cargo.toml
36
Cargo.toml
@@ -1,29 +1,32 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "deltachat"
|
name = "deltachat"
|
||||||
version = "1.0.0-alpha.4"
|
version = "1.0.0-alpha.2"
|
||||||
authors = ["dignifiedquire <dignifiedquire@gmail.com>"]
|
authors = ["dignifiedquire <dignifiedquire@gmail.com>"]
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
license = "MPL"
|
license = "MPL"
|
||||||
|
|
||||||
|
[build-dependencies]
|
||||||
|
cc = "1.0.35"
|
||||||
|
pkg-config = "0.3"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
deltachat_derive = { path = "./deltachat_derive" }
|
|
||||||
libc = "0.2.51"
|
libc = "0.2.51"
|
||||||
pgp = { version = "0.2", default-features = false }
|
pgp = { version = "0.2", default-features = false }
|
||||||
hex = "0.3.2"
|
hex = "0.3.2"
|
||||||
sha2 = "0.8.0"
|
sha2 = "0.8.0"
|
||||||
rand = "0.6.5"
|
rand = "0.6.5"
|
||||||
phf = { git = "https://github.com/sfackler/rust-phf", rev = "0d00821", features = ["macros"] }
|
|
||||||
smallvec = "0.6.9"
|
smallvec = "0.6.9"
|
||||||
|
libsqlite3-sys = { version = "0.14.0", features = ["bundled", "min_sqlite_version_3_7_16"] }
|
||||||
reqwest = "0.9.15"
|
reqwest = "0.9.15"
|
||||||
num-derive = "0.2.5"
|
num-derive = "0.2.5"
|
||||||
num-traits = "0.2.6"
|
num-traits = "0.2.6"
|
||||||
native-tls = "0.2.3"
|
native-tls = "0.2.3"
|
||||||
lettre = "0.9.0"
|
lettre = "0.9.0"
|
||||||
imap = { git = "https://github.com/jonhoo/rust-imap", rev = "281d2eb8ab50dc656ceff2ae749ca5045f334e15" }
|
imap = "1.0.1"
|
||||||
mmime = { git = "https://github.com/dignifiedquire/mmime", rev = "bccd2c2" }
|
mmime = "0.1.0"
|
||||||
base64 = "0.10"
|
base64 = "0.10"
|
||||||
charset = "0.1"
|
charset = "0.1"
|
||||||
percent-encoding = "2.0"
|
percent-encoding = "1.0"
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
chrono = "0.4.6"
|
chrono = "0.4.6"
|
||||||
@@ -31,34 +34,17 @@ failure = "0.1.5"
|
|||||||
failure_derive = "0.1.5"
|
failure_derive = "0.1.5"
|
||||||
# TODO: make optional
|
# TODO: make optional
|
||||||
rustyline = "4.1.0"
|
rustyline = "4.1.0"
|
||||||
lazy_static = "1.4.0"
|
lazy_static = "1.3.0"
|
||||||
regex = "1.1.6"
|
regex = "1.1.6"
|
||||||
rusqlite = { version = "0.20", features = ["bundled"] }
|
|
||||||
r2d2_sqlite = "0.12.0"
|
|
||||||
r2d2 = "0.8.5"
|
|
||||||
strum = "0.15.0"
|
|
||||||
strum_macros = "0.15.0"
|
|
||||||
thread-local-object = "0.1.0"
|
|
||||||
backtrace = "0.3.33"
|
|
||||||
byteorder = "1.3.1"
|
|
||||||
itertools = "0.8.0"
|
|
||||||
image-meta = "0.1.0"
|
|
||||||
quick-xml = "0.15.0"
|
|
||||||
escaper = "0.1.0"
|
|
||||||
bitflags = "1.1.0"
|
|
||||||
jetscii = "0.4.4"
|
|
||||||
debug_stub_derive = "0.3.0"
|
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
tempfile = "3.0"
|
tempfile = "3.0"
|
||||||
pretty_assertions = "0.6.1"
|
pretty_assertions = "0.6.1"
|
||||||
pretty_env_logger = "0.3.0"
|
pretty_env_logger = "0.3.0"
|
||||||
proptest = "0.9.4"
|
|
||||||
|
|
||||||
[workspace]
|
[workspace]
|
||||||
members = [
|
members = [
|
||||||
"deltachat-ffi",
|
"deltachat-ffi"
|
||||||
"deltachat_derive",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[example]]
|
[[example]]
|
||||||
|
|||||||
13
README.md
13
README.md
@@ -63,11 +63,6 @@ Single#10: yourfriends@email.org [yourfriends@email.org]
|
|||||||
Message sent.
|
Message sent.
|
||||||
```
|
```
|
||||||
|
|
||||||
If `yourfriend@email.org` uses DeltaChat, but does not receive message just
|
|
||||||
sent, it is advisable to check `Spam` folder. It is known that at least
|
|
||||||
`gmx.com` treat such test messages as spam, unless told otherwise with web
|
|
||||||
interface.
|
|
||||||
|
|
||||||
List messages when inside a chat:
|
List messages when inside a chat:
|
||||||
|
|
||||||
```
|
```
|
||||||
@@ -89,14 +84,6 @@ $ cargo test --all
|
|||||||
$ cargo build -p deltachat_ffi --release
|
$ cargo build -p deltachat_ffi --release
|
||||||
```
|
```
|
||||||
|
|
||||||
### Expensive tests
|
|
||||||
|
|
||||||
Some tests are expensive and marked with `#[ignore]`, to run these
|
|
||||||
use the `--ignored` argument to the test binary (not to cargo itself):
|
|
||||||
```sh
|
|
||||||
$ cargo test -- --ignored
|
|
||||||
```
|
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
- `vendored`: When using Openssl for TLS, this bundles a vendored version.
|
- `vendored`: When using Openssl for TLS, this bundles a vendored version.
|
||||||
|
|||||||
@@ -1,6 +0,0 @@
|
|||||||
[dependencies.std]
|
|
||||||
features = ["panic-unwind"]
|
|
||||||
|
|
||||||
# if using `cargo test`
|
|
||||||
[dependencies.test]
|
|
||||||
stage = 1
|
|
||||||
@@ -1,10 +1,11 @@
|
|||||||
environment:
|
environment:
|
||||||
matrix:
|
matrix:
|
||||||
- APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017
|
- APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017
|
||||||
|
TARGET: x86_64-pc-windows-msvc
|
||||||
|
|
||||||
install:
|
install:
|
||||||
- appveyor DownloadFile https://win.rustup.rs/ -FileName rustup-init.exe
|
- appveyor DownloadFile https://win.rustup.rs/ -FileName rustup-init.exe
|
||||||
- rustup-init -yv --default-toolchain nightly-2019-07-10
|
- rustup-init -yv --default-host %target% --default-toolchain none
|
||||||
- set PATH=%PATH%;%USERPROFILE%\.cargo\bin
|
- set PATH=%PATH%;%USERPROFILE%\.cargo\bin
|
||||||
- rustc -vV
|
- rustc -vV
|
||||||
- cargo -vV
|
- cargo -vV
|
||||||
@@ -13,7 +14,7 @@ install:
|
|||||||
build: false
|
build: false
|
||||||
|
|
||||||
test_script:
|
test_script:
|
||||||
- cargo test --release
|
- cargo test --release
|
||||||
|
|
||||||
cache:
|
cache:
|
||||||
- target
|
- target
|
||||||
|
|||||||
38
build.rs
Normal file
38
build.rs
Normal 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");
|
||||||
|
}
|
||||||
@@ -4,7 +4,6 @@ set -ex
|
|||||||
|
|
||||||
export RUST_TEST_THREADS=1
|
export RUST_TEST_THREADS=1
|
||||||
export RUST_BACKTRACE=1
|
export RUST_BACKTRACE=1
|
||||||
export RUSTFLAGS='--deny warnings'
|
|
||||||
export OPT="--target=$TARGET"
|
export OPT="--target=$TARGET"
|
||||||
export OPT_RELEASE="--release ${OPT}"
|
export OPT_RELEASE="--release ${OPT}"
|
||||||
export OPT_FFI_RELEASE="--manifest-path=deltachat-ffi/Cargo.toml --release"
|
export OPT_FFI_RELEASE="--manifest-path=deltachat-ffi/Cargo.toml --release"
|
||||||
@@ -39,4 +38,8 @@ fi
|
|||||||
|
|
||||||
# Run all the test configurations:
|
# Run all the test configurations:
|
||||||
$CARGO_CMD $CARGO_SUBCMD $OPT
|
$CARGO_CMD $CARGO_SUBCMD $OPT
|
||||||
|
$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
|
||||||
22
ci_scripts/ci_run.sh
Executable file
22
ci_scripts/ci_run.sh
Executable 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 BRANCH -e TESTS -e DOCS \
|
||||||
|
--rm -it -v $(pwd):/mnt -w /mnt \
|
||||||
|
deltachat/coredeps ci_scripts/run_all.sh
|
||||||
|
|
||||||
@@ -15,7 +15,6 @@ export BRANCH=${CIRCLE_BRANCH:?specify branch for uploading purposes}
|
|||||||
|
|
||||||
|
|
||||||
# python docs to py.delta.chat
|
# python docs to py.delta.chat
|
||||||
ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null delta@py.delta.chat mkdir -p build/${BRANCH}
|
|
||||||
rsync -avz \
|
rsync -avz \
|
||||||
-e "ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null" \
|
-e "ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null" \
|
||||||
"$PYDOCDIR/html/" \
|
"$PYDOCDIR/html/" \
|
||||||
@@ -34,17 +33,15 @@ echo -----------------------
|
|||||||
# Bundle external shared libraries into the wheels
|
# Bundle external shared libraries into the wheels
|
||||||
pushd $WHEELHOUSEDIR
|
pushd $WHEELHOUSEDIR
|
||||||
|
|
||||||
pip3 install devpi-client
|
pip install devpi-client
|
||||||
devpi use https://m.devpi.net
|
devpi use https://m.devpi.net
|
||||||
devpi login dc --password $DEVPI_LOGIN
|
devpi login dc --password $DEVPI_LOGIN
|
||||||
|
|
||||||
N_BRANCH=${BRANCH//[\/]}
|
devpi use dc/$BRANCH || {
|
||||||
|
devpi index -c $BRANCH
|
||||||
devpi use dc/$N_BRANCH || {
|
devpi use dc/$BRANCH
|
||||||
devpi index -c $N_BRANCH
|
|
||||||
devpi use dc/$N_BRANCH
|
|
||||||
}
|
}
|
||||||
devpi index $N_BRANCH bases=/root/pypi
|
devpi index $BRANCH bases=/root/pypi
|
||||||
devpi upload deltachat*.whl
|
devpi upload deltachat*.whl
|
||||||
|
|
||||||
popd
|
popd
|
||||||
|
|||||||
@@ -3,9 +3,9 @@
|
|||||||
set -e -x
|
set -e -x
|
||||||
|
|
||||||
# Install Rust
|
# Install Rust
|
||||||
curl https://sh.rustup.rs -sSf | sh -s -- --default-toolchain nightly-2019-07-10 -y
|
curl https://sh.rustup.rs -sSf | sh -s -- --default-toolchain nightly-2019-04-19 -y
|
||||||
export PATH=/root/.cargo/bin:$PATH
|
export PATH=/root/.cargo/bin:$PATH
|
||||||
rustc --version
|
rustc --version
|
||||||
|
|
||||||
# remove some 300-400 MB that we don't need for automated builds
|
# 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/
|
rm -rf /root/.rustup/toolchains/nightly-2019-04-19-x86_64-unknown-linux-gnu/share/
|
||||||
|
|||||||
@@ -36,21 +36,15 @@ if [ -n "$TESTS" ]; then
|
|||||||
rm -rf src/deltachat/__pycache__
|
rm -rf src/deltachat/__pycache__
|
||||||
export PYTHONDONTWRITEBYTECODE=1
|
export PYTHONDONTWRITEBYTECODE=1
|
||||||
|
|
||||||
# run tox. The circle-ci project env-var-setting DCC_PY_LIVECONFIG
|
# run tox
|
||||||
# allows running of "liveconfig" tests but for speed reasons
|
tox --workdir "$TOXWORKDIR" -e lint,py27,py35,py36,py37,auditwheels
|
||||||
# we run them only for the highest python version we support
|
|
||||||
|
|
||||||
tox --workdir "$TOXWORKDIR" -e py37
|
|
||||||
unset DCC_PY_LIVECONFIG
|
|
||||||
tox --workdir "$TOXWORKDIR" -p4 -e lint,py35,py36,doc
|
|
||||||
tox --workdir "$TOXWORKDIR" -e auditwheels
|
|
||||||
popd
|
popd
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|
||||||
# if [ -n "$DOCS" ]; then
|
if [ -n "$DOCS" ]; then
|
||||||
# echo -----------------------
|
echo -----------------------
|
||||||
# echo generating python docs
|
echo generating python docs
|
||||||
# echo -----------------------
|
echo -----------------------
|
||||||
# (cd python && tox --workdir "$TOXWORKDIR" -e doc)
|
(cd python && tox --workdir "$TOXWORKDIR" -e doc)
|
||||||
# fi
|
fi
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "deltachat_ffi"
|
name = "deltachat_ffi"
|
||||||
version = "1.0.0-alpha.4"
|
version = "1.0.0-alpha.1"
|
||||||
description = "Deltachat FFI"
|
description = "Deltachat FFI"
|
||||||
authors = ["dignifiedquire <dignifiedquire@gmail.com>"]
|
authors = ["dignifiedquire <dignifiedquire@gmail.com>"]
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
@@ -16,10 +16,8 @@ crate-type = ["cdylib", "staticlib"]
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
deltachat = { path = "../", default-features = false }
|
deltachat = { path = "../", default-features = false }
|
||||||
deltachat-provider-overview = { git = "https://github.com/deltachat/provider-overview", rev = "366b41a7503973e4ffac3aa5173b419f2f03c211" }
|
|
||||||
libc = "0.2"
|
libc = "0.2"
|
||||||
human-panic = "1.0.1"
|
human-panic = "1.0.1"
|
||||||
num-traits = "0.2.6"
|
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["vendored", "nightly", "ringbuf"]
|
default = ["vendored", "nightly", "ringbuf"]
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
Binary file not shown.
|
Before Width: | Height: | Size: 3.9 KiB |
@@ -1,7 +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;
|
|
||||||
}
|
|
||||||
@@ -1,10 +1 @@
|
|||||||
# Delta Chat C Interface
|
# Delta Chat C Interface
|
||||||
|
|
||||||
## 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>
|
|
||||||
|
|||||||
@@ -1,33 +0,0 @@
|
|||||||
use std::io::Write;
|
|
||||||
use std::path::PathBuf;
|
|
||||||
use std::{env, fs};
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
let out_path = PathBuf::from(env::var("OUT_DIR").unwrap());
|
|
||||||
let target_path = out_path.join("../../..");
|
|
||||||
let target_triple = env::var("TARGET").unwrap();
|
|
||||||
|
|
||||||
// macOS or iOS, inherited from rpgp
|
|
||||||
let libs_priv = if target_triple.contains("apple") || target_triple.contains("darwin") {
|
|
||||||
// needed for OsRng
|
|
||||||
"-framework Security -framework Foundation"
|
|
||||||
} else {
|
|
||||||
""
|
|
||||||
};
|
|
||||||
|
|
||||||
let pkg_config = format!(
|
|
||||||
include_str!("deltachat.pc.in"),
|
|
||||||
name = "deltachat",
|
|
||||||
description = env::var("CARGO_PKG_DESCRIPTION").unwrap(),
|
|
||||||
url = env::var("CARGO_PKG_HOMEPAGE").unwrap_or("".to_string()),
|
|
||||||
version = env::var("CARGO_PKG_VERSION").unwrap(),
|
|
||||||
libs_priv = libs_priv,
|
|
||||||
prefix = env::var("PREFIX").unwrap_or("/usr/local".to_string()),
|
|
||||||
);
|
|
||||||
|
|
||||||
fs::create_dir_all(target_path.join("pkgconfig")).unwrap();
|
|
||||||
fs::File::create(target_path.join("pkgconfig").join("deltachat.pc"))
|
|
||||||
.unwrap()
|
|
||||||
.write_all(&pkg_config.as_bytes())
|
|
||||||
.unwrap();
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,11 +0,0 @@
|
|||||||
prefix={prefix}
|
|
||||||
libdir=${{prefix}}/lib
|
|
||||||
includedir=${{prefix}}/include
|
|
||||||
|
|
||||||
Name: {name}
|
|
||||||
Description: {description}
|
|
||||||
URL: {url}
|
|
||||||
Version: {version}
|
|
||||||
Cflags: -I${{includedir}}
|
|
||||||
Libs: -L${{libdir}} -ldeltachat
|
|
||||||
Libs.private: {libs_priv}
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,92 +0,0 @@
|
|||||||
extern crate deltachat_provider_overview;
|
|
||||||
|
|
||||||
use std::ptr;
|
|
||||||
|
|
||||||
use deltachat::dc_tools::{as_str, StrExt};
|
|
||||||
use deltachat_provider_overview::StatusState;
|
|
||||||
|
|
||||||
#[no_mangle]
|
|
||||||
pub type dc_provider_t = deltachat_provider_overview::Provider;
|
|
||||||
|
|
||||||
#[no_mangle]
|
|
||||||
pub unsafe extern "C" fn dc_provider_new_from_domain(
|
|
||||||
domain: *const libc::c_char,
|
|
||||||
) -> *const dc_provider_t {
|
|
||||||
match deltachat_provider_overview::get_provider_info(as_str(domain)) {
|
|
||||||
Some(provider) => provider,
|
|
||||||
None => ptr::null(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[no_mangle]
|
|
||||||
pub unsafe extern "C" fn dc_provider_new_from_email(
|
|
||||||
email: *const libc::c_char,
|
|
||||||
) -> *const dc_provider_t {
|
|
||||||
let domain = deltachat_provider_overview::get_domain_from_email(as_str(email));
|
|
||||||
match deltachat_provider_overview::get_provider_info(domain) {
|
|
||||||
Some(provider) => provider,
|
|
||||||
None => ptr::null(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
macro_rules! null_guard {
|
|
||||||
($context:tt) => {
|
|
||||||
if $context.is_null() {
|
|
||||||
return ptr::null_mut() as *mut libc::c_char;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
#[no_mangle]
|
|
||||||
pub unsafe extern "C" fn dc_provider_get_overview_page(
|
|
||||||
provider: *const dc_provider_t,
|
|
||||||
) -> *mut libc::c_char {
|
|
||||||
null_guard!(provider);
|
|
||||||
format!(
|
|
||||||
"{}/{}",
|
|
||||||
deltachat_provider_overview::PROVIDER_OVERVIEW_URL,
|
|
||||||
(*provider).overview_page
|
|
||||||
)
|
|
||||||
.strdup()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[no_mangle]
|
|
||||||
pub unsafe extern "C" fn dc_provider_get_name(provider: *const dc_provider_t) -> *mut libc::c_char {
|
|
||||||
null_guard!(provider);
|
|
||||||
(*provider).name.strdup()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[no_mangle]
|
|
||||||
pub unsafe extern "C" fn dc_provider_get_markdown(
|
|
||||||
provider: *const dc_provider_t,
|
|
||||||
) -> *mut libc::c_char {
|
|
||||||
null_guard!(provider);
|
|
||||||
(*provider).markdown.strdup()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[no_mangle]
|
|
||||||
pub unsafe extern "C" fn dc_provider_get_status_date(
|
|
||||||
provider: *const dc_provider_t,
|
|
||||||
) -> *mut libc::c_char {
|
|
||||||
null_guard!(provider);
|
|
||||||
(*provider).status.date.strdup()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[no_mangle]
|
|
||||||
pub unsafe extern "C" fn dc_provider_get_status(provider: *const dc_provider_t) -> u32 {
|
|
||||||
if provider.is_null() {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
match (*provider).status.state {
|
|
||||||
StatusState::OK => 1,
|
|
||||||
StatusState::PREPARATION => 2,
|
|
||||||
StatusState::BROKEN => 3,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[no_mangle]
|
|
||||||
pub unsafe extern "C" fn dc_provider_unref(_provider: *const dc_provider_t) {
|
|
||||||
()
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO expose general provider overview url?
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "deltachat_derive"
|
|
||||||
version = "0.1.0"
|
|
||||||
authors = ["Dmitry Bogatov <KAction@debian.org>"]
|
|
||||||
edition = "2018"
|
|
||||||
|
|
||||||
[lib]
|
|
||||||
proc-macro = true
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
syn = "0.14.4"
|
|
||||||
quote = "0.6.3"
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
#![recursion_limit = "128"]
|
|
||||||
extern crate proc_macro;
|
|
||||||
|
|
||||||
use crate::proc_macro::TokenStream;
|
|
||||||
use quote::quote;
|
|
||||||
use syn;
|
|
||||||
|
|
||||||
// For now, assume (not check) that these macroses are applied to enum without
|
|
||||||
// data. If this assumption is violated, compiler error will point to
|
|
||||||
// generated code, which is not very user-friendly.
|
|
||||||
|
|
||||||
#[proc_macro_derive(ToSql)]
|
|
||||||
pub fn to_sql_derive(input: TokenStream) -> TokenStream {
|
|
||||||
let ast: syn::DeriveInput = syn::parse(input).unwrap();
|
|
||||||
let name = &ast.ident;
|
|
||||||
|
|
||||||
let gen = quote! {
|
|
||||||
impl rusqlite::types::ToSql for #name {
|
|
||||||
fn to_sql(&self) -> rusqlite::Result<rusqlite::types::ToSqlOutput> {
|
|
||||||
let num = *self as i64;
|
|
||||||
let value = rusqlite::types::Value::Integer(num);
|
|
||||||
let output = rusqlite::types::ToSqlOutput::Owned(value);
|
|
||||||
std::result::Result::Ok(output)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
gen.into()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[proc_macro_derive(FromSql)]
|
|
||||||
pub fn from_sql_derive(input: TokenStream) -> TokenStream {
|
|
||||||
let ast: syn::DeriveInput = syn::parse(input).unwrap();
|
|
||||||
let name = &ast.ident;
|
|
||||||
|
|
||||||
let gen = quote! {
|
|
||||||
impl rusqlite::types::FromSql for #name {
|
|
||||||
fn column_result(col: rusqlite::types::ValueRef) -> rusqlite::types::FromSqlResult<Self> {
|
|
||||||
let inner = rusqlite::types::FromSql::column_result(col)?;
|
|
||||||
Ok(num_traits::FromPrimitive::from_i64(inner).unwrap_or_default())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
gen.into()
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -10,25 +10,20 @@ extern crate deltachat;
|
|||||||
extern crate failure;
|
extern crate failure;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate lazy_static;
|
extern crate lazy_static;
|
||||||
#[macro_use]
|
|
||||||
extern crate rusqlite;
|
|
||||||
|
|
||||||
use std::borrow::Cow::{self, Borrowed, Owned};
|
use std::borrow::Cow::{self, Borrowed, Owned};
|
||||||
use std::io::{self, Write};
|
|
||||||
use std::path::Path;
|
|
||||||
use std::process::Command;
|
|
||||||
use std::sync::atomic::{AtomicBool, Ordering};
|
use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
use std::sync::{Arc, Mutex, RwLock};
|
use std::sync::{Arc, Mutex, RwLock};
|
||||||
|
|
||||||
use deltachat::config;
|
use deltachat::constants::*;
|
||||||
use deltachat::configure::*;
|
|
||||||
use deltachat::context::*;
|
use deltachat::context::*;
|
||||||
|
use deltachat::dc_configure::*;
|
||||||
|
use deltachat::dc_job::*;
|
||||||
|
use deltachat::dc_securejoin::*;
|
||||||
use deltachat::dc_tools::*;
|
use deltachat::dc_tools::*;
|
||||||
use deltachat::job::*;
|
|
||||||
use deltachat::oauth2::*;
|
use deltachat::oauth2::*;
|
||||||
use deltachat::securejoin::*;
|
use deltachat::types::*;
|
||||||
use deltachat::x::*;
|
use deltachat::x::*;
|
||||||
use deltachat::Event;
|
|
||||||
use rustyline::completion::{Completer, FilenameCompleter, Pair};
|
use rustyline::completion::{Completer, FilenameCompleter, Pair};
|
||||||
use rustyline::config::OutputStreamType;
|
use rustyline::config::OutputStreamType;
|
||||||
use rustyline::error::ReadlineError;
|
use rustyline::error::ReadlineError;
|
||||||
@@ -43,75 +38,96 @@ use self::cmdline::*;
|
|||||||
|
|
||||||
// Event Handler
|
// Event Handler
|
||||||
|
|
||||||
fn receive_event(_context: &Context, event: Event) -> libc::uintptr_t {
|
unsafe extern "C" fn receive_event(
|
||||||
|
_context: &Context,
|
||||||
|
event: Event,
|
||||||
|
data1: uintptr_t,
|
||||||
|
data2: uintptr_t,
|
||||||
|
) -> uintptr_t {
|
||||||
match event {
|
match event {
|
||||||
Event::GetString { .. } => {}
|
Event::GET_STRING => {}
|
||||||
Event::Info(msg) => {
|
Event::INFO => {
|
||||||
/* do not show the event as this would fill the screen */
|
/* do not show the event as this would fill the screen */
|
||||||
println!("{}", msg);
|
println!("{}", to_string(data2 as *const _),);
|
||||||
}
|
}
|
||||||
Event::SmtpConnected(msg) => {
|
Event::SMTP_CONNECTED => {
|
||||||
println!("[DC_EVENT_SMTP_CONNECTED] {}", msg);
|
println!("[DC_EVENT_SMTP_CONNECTED] {}", to_string(data2 as *const _));
|
||||||
}
|
}
|
||||||
Event::ImapConnected(msg) => {
|
Event::IMAP_CONNECTED => {
|
||||||
println!("[DC_EVENT_IMAP_CONNECTED] {}", msg);
|
println!("[DC_EVENT_IMAP_CONNECTED] {}", to_string(data2 as *const _),);
|
||||||
}
|
}
|
||||||
Event::SmtpMessageSent(msg) => {
|
Event::SMTP_MESSAGE_SENT => {
|
||||||
println!("[DC_EVENT_SMTP_MESSAGE_SENT] {}", msg);
|
println!(
|
||||||
}
|
"[DC_EVENT_SMTP_MESSAGE_SENT] {}",
|
||||||
Event::Warning(msg) => {
|
to_string(data2 as *const _),
|
||||||
println!("[Warning] {}", msg);
|
|
||||||
}
|
|
||||||
Event::Error(msg) => {
|
|
||||||
println!("\x1b[31m[DC_EVENT_ERROR] {}\x1b[0m", msg);
|
|
||||||
}
|
|
||||||
Event::ErrorNetwork(msg) => {
|
|
||||||
println!("\x1b[31m[DC_EVENT_ERROR_NETWORK] msg={}\x1b[0m", msg);
|
|
||||||
}
|
|
||||||
Event::ErrorSelfNotInGroup(msg) => {
|
|
||||||
println!("\x1b[31m[DC_EVENT_ERROR_SELF_NOT_IN_GROUP] {}\x1b[0m", msg);
|
|
||||||
}
|
|
||||||
Event::MsgsChanged { chat_id, msg_id } => {
|
|
||||||
print!(
|
|
||||||
"\x1b[33m{{Received DC_EVENT_MSGS_CHANGED(chat_id={}, msg_id={})}}\n\x1b[0m",
|
|
||||||
chat_id, msg_id,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
Event::ContactsChanged(_) => {
|
Event::WARNING => {
|
||||||
|
println!("[Warning] {}", to_string(data2 as *const _),);
|
||||||
|
}
|
||||||
|
Event::ERROR => {
|
||||||
|
println!(
|
||||||
|
"\x1b[31m[DC_EVENT_ERROR] {}\x1b[0m",
|
||||||
|
to_string(data2 as *const _),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Event::ERROR_NETWORK => {
|
||||||
|
println!(
|
||||||
|
"\x1b[31m[DC_EVENT_ERROR_NETWORK] first={}, msg={}\x1b[0m",
|
||||||
|
data1 as usize,
|
||||||
|
to_string(data2 as *const _),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Event::ERROR_SELF_NOT_IN_GROUP => {
|
||||||
|
println!(
|
||||||
|
"\x1b[31m[DC_EVENT_ERROR_SELF_NOT_IN_GROUP] {}\x1b[0m",
|
||||||
|
to_string(data2 as *const _),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Event::MSGS_CHANGED => {
|
||||||
|
print!(
|
||||||
|
"\x1b[33m{{Received DC_EVENT_MSGS_CHANGED({}, {})}}\n\x1b[0m",
|
||||||
|
data1 as usize, data2 as usize,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Event::CONTACTS_CHANGED => {
|
||||||
print!("\x1b[33m{{Received DC_EVENT_CONTACTS_CHANGED()}}\n\x1b[0m");
|
print!("\x1b[33m{{Received DC_EVENT_CONTACTS_CHANGED()}}\n\x1b[0m");
|
||||||
}
|
}
|
||||||
Event::LocationChanged(contact) => {
|
Event::LOCATION_CHANGED => {
|
||||||
print!(
|
print!(
|
||||||
"\x1b[33m{{Received DC_EVENT_LOCATION_CHANGED(contact={:?})}}\n\x1b[0m",
|
"\x1b[33m{{Received DC_EVENT_LOCATION_CHANGED(contact={})}}\n\x1b[0m",
|
||||||
contact,
|
data1 as usize,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
Event::ConfigureProgress(progress) => {
|
Event::CONFIGURE_PROGRESS => {
|
||||||
print!(
|
print!(
|
||||||
"\x1b[33m{{Received DC_EVENT_CONFIGURE_PROGRESS({} ‰)}}\n\x1b[0m",
|
"\x1b[33m{{Received DC_EVENT_CONFIGURE_PROGRESS({} ‰)}}\n\x1b[0m",
|
||||||
progress,
|
data1 as usize,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
Event::ImexProgress(progress) => {
|
Event::IMEX_PROGRESS => {
|
||||||
print!(
|
print!(
|
||||||
"\x1b[33m{{Received DC_EVENT_IMEX_PROGRESS({} ‰)}}\n\x1b[0m",
|
"\x1b[33m{{Received DC_EVENT_IMEX_PROGRESS({} ‰)}}\n\x1b[0m",
|
||||||
progress,
|
data1 as usize,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
Event::ImexFileWritten(file) => {
|
Event::IMEX_FILE_WRITTEN => {
|
||||||
print!(
|
print!(
|
||||||
"\x1b[33m{{Received DC_EVENT_IMEX_FILE_WRITTEN({})}}\n\x1b[0m",
|
"\x1b[33m{{Received DC_EVENT_IMEX_FILE_WRITTEN({})}}\n\x1b[0m",
|
||||||
file.display()
|
to_string(data1 as *const _)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
Event::ChatModified(chat) => {
|
Event::CHAT_MODIFIED => {
|
||||||
print!(
|
print!(
|
||||||
"\x1b[33m{{Received DC_EVENT_CHAT_MODIFIED({})}}\n\x1b[0m",
|
"\x1b[33m{{Received DC_EVENT_CHAT_MODIFIED({})}}\n\x1b[0m",
|
||||||
chat
|
data1 as usize,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
print!("\x1b[33m{{Received {:?}}}\n\x1b[0m", event);
|
print!(
|
||||||
|
"\x1b[33m{{Received {:?}({}, {})}}\n\x1b[0m",
|
||||||
|
event, data1 as usize, data2 as usize,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -153,11 +169,13 @@ fn start_threads(c: Arc<RwLock<Context>>) {
|
|||||||
let ctx = c.clone();
|
let ctx = c.clone();
|
||||||
let handle_imap = std::thread::spawn(move || loop {
|
let handle_imap = std::thread::spawn(move || loop {
|
||||||
while_running!({
|
while_running!({
|
||||||
perform_imap_jobs(&ctx.read().unwrap());
|
unsafe {
|
||||||
perform_imap_fetch(&ctx.read().unwrap());
|
dc_perform_imap_jobs(&ctx.read().unwrap());
|
||||||
|
dc_perform_imap_fetch(&ctx.read().unwrap());
|
||||||
|
}
|
||||||
while_running!({
|
while_running!({
|
||||||
let context = ctx.read().unwrap();
|
let context = ctx.read().unwrap();
|
||||||
perform_imap_idle(&context);
|
dc_perform_imap_idle(&context);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -165,9 +183,9 @@ fn start_threads(c: Arc<RwLock<Context>>) {
|
|||||||
let ctx = c.clone();
|
let ctx = c.clone();
|
||||||
let handle_mvbox = std::thread::spawn(move || loop {
|
let handle_mvbox = std::thread::spawn(move || loop {
|
||||||
while_running!({
|
while_running!({
|
||||||
perform_mvbox_fetch(&ctx.read().unwrap());
|
unsafe { dc_perform_mvbox_fetch(&ctx.read().unwrap()) };
|
||||||
while_running!({
|
while_running!({
|
||||||
perform_mvbox_idle(&ctx.read().unwrap());
|
unsafe { dc_perform_mvbox_idle(&ctx.read().unwrap()) };
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -175,9 +193,9 @@ fn start_threads(c: Arc<RwLock<Context>>) {
|
|||||||
let ctx = c.clone();
|
let ctx = c.clone();
|
||||||
let handle_sentbox = std::thread::spawn(move || loop {
|
let handle_sentbox = std::thread::spawn(move || loop {
|
||||||
while_running!({
|
while_running!({
|
||||||
perform_sentbox_fetch(&ctx.read().unwrap());
|
unsafe { dc_perform_sentbox_fetch(&ctx.read().unwrap()) };
|
||||||
while_running!({
|
while_running!({
|
||||||
perform_sentbox_idle(&ctx.read().unwrap());
|
unsafe { dc_perform_sentbox_idle(&ctx.read().unwrap()) };
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -185,9 +203,9 @@ fn start_threads(c: Arc<RwLock<Context>>) {
|
|||||||
let ctx = c;
|
let ctx = c;
|
||||||
let handle_smtp = std::thread::spawn(move || loop {
|
let handle_smtp = std::thread::spawn(move || loop {
|
||||||
while_running!({
|
while_running!({
|
||||||
perform_smtp_jobs(&ctx.read().unwrap());
|
unsafe { dc_perform_smtp_jobs(&ctx.read().unwrap()) };
|
||||||
while_running!({
|
while_running!({
|
||||||
perform_smtp_idle(&ctx.read().unwrap());
|
unsafe { dc_perform_smtp_idle(&ctx.read().unwrap()) };
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -205,10 +223,12 @@ fn stop_threads(context: &Context) {
|
|||||||
println!("Stopping threads");
|
println!("Stopping threads");
|
||||||
IS_RUNNING.store(false, Ordering::Relaxed);
|
IS_RUNNING.store(false, Ordering::Relaxed);
|
||||||
|
|
||||||
interrupt_imap_idle(context);
|
unsafe {
|
||||||
interrupt_mvbox_idle(context);
|
dc_interrupt_imap_idle(context);
|
||||||
interrupt_sentbox_idle(context);
|
dc_interrupt_mvbox_idle(context);
|
||||||
interrupt_smtp_idle(context);
|
dc_interrupt_sentbox_idle(context);
|
||||||
|
dc_interrupt_smtp_idle(context);
|
||||||
|
}
|
||||||
|
|
||||||
handle.handle_imap.take().unwrap().join().unwrap();
|
handle.handle_imap.take().unwrap().join().unwrap();
|
||||||
handle.handle_mvbox.take().unwrap().join().unwrap();
|
handle.handle_mvbox.take().unwrap().join().unwrap();
|
||||||
@@ -311,8 +331,8 @@ const CONTACT_COMMANDS: [&'static str; 6] = [
|
|||||||
"delcontact",
|
"delcontact",
|
||||||
"cleanupcontacts",
|
"cleanupcontacts",
|
||||||
];
|
];
|
||||||
const MISC_COMMANDS: [&'static str; 9] = [
|
const MISC_COMMANDS: [&'static str; 8] = [
|
||||||
"getqr", "getbadqr", "checkqr", "event", "fileinfo", "clear", "exit", "quit", "help",
|
"getqr", "getbadqr", "checkqr", "event", "fileinfo", "clear", "exit", "help",
|
||||||
];
|
];
|
||||||
|
|
||||||
impl Hinter for DcHelper {
|
impl Hinter for DcHelper {
|
||||||
@@ -365,15 +385,27 @@ impl Highlighter for DcHelper {
|
|||||||
impl Helper for DcHelper {}
|
impl Helper for DcHelper {}
|
||||||
|
|
||||||
fn main_0(args: Vec<String>) -> Result<(), failure::Error> {
|
fn main_0(args: Vec<String>) -> Result<(), failure::Error> {
|
||||||
if args.len() < 2 {
|
let mut context = dc_context_new(
|
||||||
|
Some(receive_event),
|
||||||
|
0 as *mut libc::c_void,
|
||||||
|
b"CLI\x00" as *const u8 as *const libc::c_char,
|
||||||
|
);
|
||||||
|
|
||||||
|
unsafe { dc_cmdline_skip_auth() };
|
||||||
|
|
||||||
|
if args.len() == 2 {
|
||||||
|
if 0 == unsafe {
|
||||||
|
dc_open(
|
||||||
|
&mut context,
|
||||||
|
to_cstring(&args[1]).as_ptr(),
|
||||||
|
0 as *const libc::c_char,
|
||||||
|
)
|
||||||
|
} {
|
||||||
|
println!("Error: Cannot open {}.", args[0],);
|
||||||
|
}
|
||||||
|
} else if args.len() != 1 {
|
||||||
println!("Error: Bad arguments, expected [db-name].");
|
println!("Error: Bad arguments, expected [db-name].");
|
||||||
return Err(format_err!("No db-name specified"));
|
|
||||||
}
|
}
|
||||||
let context = Context::new(
|
|
||||||
Box::new(receive_event),
|
|
||||||
"CLI".into(),
|
|
||||||
Path::new(&args[1]).to_path_buf(),
|
|
||||||
)?;
|
|
||||||
|
|
||||||
println!("Delta Chat Core is awaiting your commands.");
|
println!("Delta Chat Core is awaiting your commands.");
|
||||||
|
|
||||||
@@ -426,6 +458,12 @@ fn main_0(args: Vec<String>) -> Result<(), failure::Error> {
|
|||||||
println!("history saved");
|
println!("history saved");
|
||||||
{
|
{
|
||||||
stop_threads(&ctx.read().unwrap());
|
stop_threads(&ctx.read().unwrap());
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
let mut ctx = ctx.write().unwrap();
|
||||||
|
dc_close(&mut ctx);
|
||||||
|
dc_context_unref(&mut ctx);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -441,10 +479,11 @@ unsafe fn handle_cmd(line: &str, ctx: Arc<RwLock<Context>>) -> Result<ExitResult
|
|||||||
let mut args = line.splitn(2, ' ');
|
let mut args = line.splitn(2, ' ');
|
||||||
let arg0 = args.next().unwrap_or_default();
|
let arg0 = args.next().unwrap_or_default();
|
||||||
let arg1 = args.next().unwrap_or_default();
|
let arg1 = args.next().unwrap_or_default();
|
||||||
let arg1_c = if arg1.is_empty() {
|
let arg1_c = to_cstring(arg1);
|
||||||
|
let arg1_c_ptr = if arg1.is_empty() {
|
||||||
std::ptr::null()
|
std::ptr::null()
|
||||||
} else {
|
} else {
|
||||||
arg1.strdup()
|
arg1_c.as_ptr()
|
||||||
};
|
};
|
||||||
|
|
||||||
match arg0 {
|
match arg0 {
|
||||||
@@ -458,35 +497,40 @@ unsafe fn handle_cmd(line: &str, ctx: Arc<RwLock<Context>>) -> Result<ExitResult
|
|||||||
if HANDLE.clone().lock().unwrap().is_some() {
|
if HANDLE.clone().lock().unwrap().is_some() {
|
||||||
println!("smtp-jobs are already running in a thread.",);
|
println!("smtp-jobs are already running in a thread.",);
|
||||||
} else {
|
} else {
|
||||||
perform_smtp_jobs(&ctx.read().unwrap());
|
dc_perform_smtp_jobs(&ctx.read().unwrap());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"imap-jobs" => {
|
"imap-jobs" => {
|
||||||
if HANDLE.clone().lock().unwrap().is_some() {
|
if HANDLE.clone().lock().unwrap().is_some() {
|
||||||
println!("imap-jobs are already running in a thread.");
|
println!("imap-jobs are already running in a thread.");
|
||||||
} else {
|
} else {
|
||||||
perform_imap_jobs(&ctx.read().unwrap());
|
dc_perform_imap_jobs(&ctx.read().unwrap());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"configure" => {
|
"configure" => {
|
||||||
start_threads(ctx.clone());
|
start_threads(ctx.clone());
|
||||||
configure(&ctx.read().unwrap());
|
dc_configure(&ctx.read().unwrap());
|
||||||
}
|
}
|
||||||
"oauth2" => {
|
"oauth2" => {
|
||||||
if let Some(addr) = ctx.read().unwrap().get_config(config::Config::Addr) {
|
let addr = dc_get_config(
|
||||||
|
&ctx.read().unwrap(),
|
||||||
|
b"addr\x00" as *const u8 as *const libc::c_char,
|
||||||
|
);
|
||||||
|
if addr.is_null() || *addr.offset(0isize) as libc::c_int == 0i32 {
|
||||||
|
println!("oauth2: set addr first.");
|
||||||
|
} else {
|
||||||
let oauth2_url = dc_get_oauth2_url(
|
let oauth2_url = dc_get_oauth2_url(
|
||||||
&ctx.read().unwrap(),
|
&ctx.read().unwrap(),
|
||||||
&addr,
|
as_str(addr),
|
||||||
"chat.delta:/com.b44t.messenger",
|
"chat.delta:/com.b44t.messenger",
|
||||||
);
|
);
|
||||||
if oauth2_url.is_none() {
|
if oauth2_url.is_none() {
|
||||||
println!("OAuth2 not available for {}.", &addr);
|
println!("OAuth2 not available for {}.", to_string(addr));
|
||||||
} else {
|
} else {
|
||||||
println!("Open the following url, set mail_pw to the generated token and server_flags to 2:\n{}", oauth2_url.unwrap());
|
println!("Open the following url, set mail_pw to the generated token and server_flags to 2:\n{}", oauth2_url.unwrap());
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
println!("oauth2: set addr first.");
|
|
||||||
}
|
}
|
||||||
|
free(addr as *mut libc::c_void);
|
||||||
}
|
}
|
||||||
"clear" => {
|
"clear" => {
|
||||||
println!("\n\n\n");
|
println!("\n\n\n");
|
||||||
@@ -494,35 +538,36 @@ unsafe fn handle_cmd(line: &str, ctx: Arc<RwLock<Context>>) -> Result<ExitResult
|
|||||||
}
|
}
|
||||||
"getqr" | "getbadqr" => {
|
"getqr" | "getbadqr" => {
|
||||||
start_threads(ctx.clone());
|
start_threads(ctx.clone());
|
||||||
if let Some(mut qr) =
|
let qrstr =
|
||||||
dc_get_securejoin_qr(&ctx.read().unwrap(), arg1.parse().unwrap_or_default())
|
dc_get_securejoin_qr(&ctx.read().unwrap(), arg1.parse().unwrap_or_default());
|
||||||
{
|
if !qrstr.is_null() && 0 != *qrstr.offset(0isize) as libc::c_int {
|
||||||
if !qr.is_empty() {
|
if arg0 == "getbadqr" && strlen(qrstr) > 40 {
|
||||||
if arg0 == "getbadqr" && qr.len() > 40 {
|
let mut i: libc::c_int = 12i32;
|
||||||
qr.replace_range(12..22, "0000000000")
|
while i < 22i32 {
|
||||||
|
*qrstr.offset(i as isize) = '0' as i32 as libc::c_char;
|
||||||
|
i += 1
|
||||||
}
|
}
|
||||||
println!("{}", qr);
|
|
||||||
let output = Command::new("qrencode")
|
|
||||||
.args(&["-t", "ansiutf8", qr.as_str(), "-o", "-"])
|
|
||||||
.output()
|
|
||||||
.expect("failed to execute process");
|
|
||||||
io::stdout().write_all(&output.stdout).unwrap();
|
|
||||||
io::stderr().write_all(&output.stderr).unwrap();
|
|
||||||
}
|
}
|
||||||
|
println!("{}", to_string(qrstr as *const _));
|
||||||
|
let syscmd = dc_mprintf(
|
||||||
|
b"qrencode -t ansiutf8 \"%s\" -o -\x00" as *const u8 as *const libc::c_char,
|
||||||
|
qrstr,
|
||||||
|
);
|
||||||
|
system(syscmd);
|
||||||
|
free(syscmd as *mut libc::c_void);
|
||||||
}
|
}
|
||||||
|
free(qrstr as *mut libc::c_void);
|
||||||
}
|
}
|
||||||
"joinqr" => {
|
"joinqr" => {
|
||||||
start_threads(ctx.clone());
|
start_threads(ctx.clone());
|
||||||
if !arg0.is_empty() {
|
if !arg0.is_empty() {
|
||||||
dc_join_securejoin(&ctx.read().unwrap(), arg1);
|
dc_join_securejoin(&ctx.read().unwrap(), arg1_c_ptr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"exit" | "quit" => return Ok(ExitResult::Exit),
|
"exit" => return Ok(ExitResult::Exit),
|
||||||
_ => dc_cmdline(&ctx.read().unwrap(), line)?,
|
_ => dc_cmdline(&ctx.read().unwrap(), line)?,
|
||||||
}
|
}
|
||||||
|
|
||||||
free(arg1_c as *mut _);
|
|
||||||
|
|
||||||
Ok(ExitResult::Continue)
|
Ok(ExitResult::Continue)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,62 +1,63 @@
|
|||||||
extern crate deltachat;
|
extern crate deltachat;
|
||||||
|
|
||||||
|
use std::ffi::{CStr, CString};
|
||||||
use std::sync::{Arc, RwLock};
|
use std::sync::{Arc, RwLock};
|
||||||
use std::{thread, time};
|
use std::{thread, time};
|
||||||
use tempfile::tempdir;
|
use tempfile::tempdir;
|
||||||
|
|
||||||
use deltachat::chat;
|
use deltachat::constants::Event;
|
||||||
use deltachat::chatlist::*;
|
|
||||||
use deltachat::config;
|
|
||||||
use deltachat::configure::*;
|
|
||||||
use deltachat::contact::*;
|
|
||||||
use deltachat::context::*;
|
use deltachat::context::*;
|
||||||
use deltachat::job::{
|
use deltachat::dc_chat::*;
|
||||||
perform_imap_fetch, perform_imap_idle, perform_imap_jobs, perform_smtp_idle, perform_smtp_jobs,
|
use deltachat::dc_chatlist::*;
|
||||||
|
use deltachat::dc_configure::*;
|
||||||
|
use deltachat::dc_contact::*;
|
||||||
|
use deltachat::dc_job::{
|
||||||
|
dc_perform_imap_fetch, dc_perform_imap_idle, dc_perform_imap_jobs, dc_perform_smtp_idle,
|
||||||
|
dc_perform_smtp_jobs,
|
||||||
};
|
};
|
||||||
use deltachat::Event;
|
use deltachat::dc_lot::*;
|
||||||
|
|
||||||
fn cb(_ctx: &Context, event: Event) -> usize {
|
extern "C" fn cb(_ctx: &Context, event: Event, data1: usize, data2: usize) -> usize {
|
||||||
print!("[{:?}]", event);
|
println!("[{:?}]", event);
|
||||||
|
|
||||||
match event {
|
match event {
|
||||||
Event::ConfigureProgress(progress) => {
|
Event::CONFIGURE_PROGRESS => {
|
||||||
print!(" progress: {}\n", progress);
|
println!(" progress: {}", data1);
|
||||||
0
|
0
|
||||||
}
|
}
|
||||||
Event::Info(msg) | Event::Warning(msg) | Event::Error(msg) | Event::ErrorNetwork(msg) => {
|
Event::INFO | Event::WARNING | Event::ERROR | Event::ERROR_NETWORK => {
|
||||||
print!(" {}\n", msg);
|
println!(
|
||||||
0
|
" {}",
|
||||||
}
|
unsafe { CStr::from_ptr(data2 as *const _) }
|
||||||
_ => {
|
.to_str()
|
||||||
print!("\n");
|
.unwrap()
|
||||||
|
);
|
||||||
0
|
0
|
||||||
}
|
}
|
||||||
|
_ => 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
unsafe {
|
unsafe {
|
||||||
let dir = tempdir().unwrap();
|
let ctx = dc_context_new(Some(cb), std::ptr::null_mut(), std::ptr::null_mut());
|
||||||
let dbfile = dir.path().join("db.sqlite");
|
|
||||||
println!("creating database {:?}", dbfile);
|
|
||||||
let ctx =
|
|
||||||
Context::new(Box::new(cb), "FakeOs".into(), dbfile).expect("Failed to create context");
|
|
||||||
let running = Arc::new(RwLock::new(true));
|
let running = Arc::new(RwLock::new(true));
|
||||||
let info = ctx.get_info();
|
let info = dc_get_info(&ctx);
|
||||||
|
let info_s = CStr::from_ptr(info);
|
||||||
let duration = time::Duration::from_millis(4000);
|
let duration = time::Duration::from_millis(4000);
|
||||||
println!("info: {:#?}", info);
|
println!("info: {}", info_s.to_str().unwrap());
|
||||||
|
|
||||||
let ctx = Arc::new(ctx);
|
let ctx = Arc::new(ctx);
|
||||||
let ctx1 = ctx.clone();
|
let ctx1 = ctx.clone();
|
||||||
let r1 = running.clone();
|
let r1 = running.clone();
|
||||||
let t1 = thread::spawn(move || {
|
let t1 = thread::spawn(move || {
|
||||||
while *r1.read().unwrap() {
|
while *r1.read().unwrap() {
|
||||||
perform_imap_jobs(&ctx1);
|
dc_perform_imap_jobs(&ctx1);
|
||||||
if *r1.read().unwrap() {
|
if *r1.read().unwrap() {
|
||||||
perform_imap_fetch(&ctx1);
|
dc_perform_imap_fetch(&ctx1);
|
||||||
|
|
||||||
if *r1.read().unwrap() {
|
if *r1.read().unwrap() {
|
||||||
perform_imap_idle(&ctx1);
|
dc_perform_imap_idle(&ctx1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -66,42 +67,68 @@ fn main() {
|
|||||||
let r1 = running.clone();
|
let r1 = running.clone();
|
||||||
let t2 = thread::spawn(move || {
|
let t2 = thread::spawn(move || {
|
||||||
while *r1.read().unwrap() {
|
while *r1.read().unwrap() {
|
||||||
perform_smtp_jobs(&ctx1);
|
dc_perform_smtp_jobs(&ctx1);
|
||||||
if *r1.read().unwrap() {
|
if *r1.read().unwrap() {
|
||||||
perform_smtp_idle(&ctx1);
|
dc_perform_smtp_idle(&ctx1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let dir = tempdir().unwrap();
|
||||||
|
let dbfile = CString::new(dir.path().join("db.sqlite").to_str().unwrap()).unwrap();
|
||||||
|
|
||||||
|
println!("opening database {:?}", dbfile);
|
||||||
|
|
||||||
|
dc_open(&ctx, dbfile.as_ptr(), std::ptr::null());
|
||||||
|
|
||||||
println!("configuring");
|
println!("configuring");
|
||||||
let args = std::env::args().collect::<Vec<String>>();
|
let pw = std::env::args().collect::<Vec<String>>()[1].clone();
|
||||||
assert_eq!(args.len(), 2, "missing password");
|
dc_set_config(
|
||||||
let pw = args[1].clone();
|
&ctx,
|
||||||
ctx.set_config(config::Config::Addr, Some("d@testrun.org"))
|
CString::new("addr").unwrap().as_ptr(),
|
||||||
.unwrap();
|
CString::new("d@testrun.org").unwrap().as_ptr(),
|
||||||
ctx.set_config(config::Config::MailPw, Some(&pw)).unwrap();
|
);
|
||||||
configure(&ctx);
|
dc_set_config(
|
||||||
|
&ctx,
|
||||||
|
CString::new("mail_pw").unwrap().as_ptr(),
|
||||||
|
CString::new(pw).unwrap().as_ptr(),
|
||||||
|
);
|
||||||
|
dc_configure(&ctx);
|
||||||
|
|
||||||
thread::sleep(duration);
|
thread::sleep(duration);
|
||||||
|
|
||||||
|
let email = CString::new("dignifiedquire@gmail.com").unwrap();
|
||||||
println!("sending a message");
|
println!("sending a message");
|
||||||
let contact_id =
|
let contact_id = dc_create_contact(&ctx, std::ptr::null(), email.as_ptr());
|
||||||
Contact::create(&ctx, "dignifiedquire", "dignifiedquire@gmail.com").unwrap();
|
let chat_id = dc_create_chat_by_contact_id(&ctx, contact_id);
|
||||||
let chat_id = chat::create_by_contact_id(&ctx, contact_id).unwrap();
|
let msg_text = CString::new("Hi, here is my first message!").unwrap();
|
||||||
chat::send_text_msg(&ctx, chat_id, "Hi, here is my first message!".into()).unwrap();
|
dc_send_text_msg(&ctx, chat_id, msg_text.as_ptr());
|
||||||
|
|
||||||
println!("fetching chats..");
|
println!("fetching chats..");
|
||||||
let chats = Chatlist::try_load(&ctx, 0, None, None).unwrap();
|
let chats = dc_get_chatlist(&ctx, 0, std::ptr::null(), 0);
|
||||||
|
|
||||||
for i in 0..chats.len() {
|
for i in 0..dc_chatlist_get_cnt(chats) {
|
||||||
let summary = chats.get_summary(&ctx, 0, None);
|
let summary = dc_chatlist_get_summary(chats, 0, std::ptr::null_mut());
|
||||||
let text1 = summary.get_text1();
|
let text1 = dc_lot_get_text1(summary);
|
||||||
let text2 = summary.get_text2();
|
let text2 = dc_lot_get_text2(summary);
|
||||||
println!("chat: {} - {:?} - {:?}", i, text1, text2,);
|
|
||||||
|
let text1_s = if !text1.is_null() {
|
||||||
|
Some(CStr::from_ptr(text1))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
let text2_s = if !text2.is_null() {
|
||||||
|
Some(CStr::from_ptr(text2))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
println!("chat: {} - {:?} - {:?}", i, text1_s, text2_s,);
|
||||||
|
dc_lot_unref(summary);
|
||||||
}
|
}
|
||||||
|
dc_chatlist_unref(chats);
|
||||||
|
|
||||||
thread::sleep(duration);
|
*running.clone().write().unwrap() = false;
|
||||||
|
println!("stopping threads");
|
||||||
// let msglist = dc_get_chat_msgs(&ctx, chat_id, 0, 0);
|
// let msglist = dc_get_chat_msgs(&ctx, chat_id, 0, 0);
|
||||||
// for i in 0..dc_array_get_cnt(msglist) {
|
// for i in 0..dc_array_get_cnt(msglist) {
|
||||||
// let msg_id = dc_array_get_id(msglist, i);
|
// let msg_id = dc_array_get_id(msglist, i);
|
||||||
@@ -112,16 +139,14 @@ fn main() {
|
|||||||
// }
|
// }
|
||||||
// dc_array_unref(msglist);
|
// dc_array_unref(msglist);
|
||||||
|
|
||||||
println!("stopping threads");
|
deltachat::dc_job::dc_interrupt_imap_idle(&ctx);
|
||||||
|
deltachat::dc_job::dc_interrupt_smtp_idle(&ctx);
|
||||||
*running.clone().write().unwrap() = false;
|
|
||||||
deltachat::job::interrupt_imap_idle(&ctx);
|
|
||||||
deltachat::job::interrupt_smtp_idle(&ctx);
|
|
||||||
|
|
||||||
println!("joining");
|
println!("joining");
|
||||||
t1.join().unwrap();
|
t1.join().unwrap();
|
||||||
t2.join().unwrap();
|
t2.join().unwrap();
|
||||||
|
|
||||||
println!("closing");
|
println!("closing");
|
||||||
|
dc_close(&ctx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
52
misc.c
Normal file
52
misc.c
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdarg.h>
|
||||||
|
#include <ctype.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include "misc.h"
|
||||||
|
|
||||||
|
|
||||||
|
static char* internal_dc_strdup(const char* s) /* strdup(NULL) is undefined, save_strdup(NULL) returns an empty string in this case */
|
||||||
|
{
|
||||||
|
char* ret = NULL;
|
||||||
|
if (s) {
|
||||||
|
if ((ret=strdup(s))==NULL) {
|
||||||
|
exit(16); /* cannot allocate (little) memory, unrecoverable error */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if ((ret=(char*)calloc(1, 1))==NULL) {
|
||||||
|
exit(17); /* cannot allocate little memory, unrecoverable error */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
char* dc_mprintf(const char* format, ...)
|
||||||
|
{
|
||||||
|
char testbuf[1];
|
||||||
|
char* buf = NULL;
|
||||||
|
int char_cnt_without_zero = 0;
|
||||||
|
|
||||||
|
va_list argp;
|
||||||
|
va_list argp_copy;
|
||||||
|
va_start(argp, format);
|
||||||
|
va_copy(argp_copy, argp);
|
||||||
|
|
||||||
|
char_cnt_without_zero = vsnprintf(testbuf, 0, format, argp);
|
||||||
|
va_end(argp);
|
||||||
|
if (char_cnt_without_zero < 0) {
|
||||||
|
va_end(argp_copy);
|
||||||
|
return internal_dc_strdup("ErrFmt");
|
||||||
|
}
|
||||||
|
|
||||||
|
buf = malloc(char_cnt_without_zero+2 /* +1 would be enough, however, protect against off-by-one-errors */);
|
||||||
|
if (buf==NULL) {
|
||||||
|
va_end(argp_copy);
|
||||||
|
return internal_dc_strdup("ErrMem");
|
||||||
|
}
|
||||||
|
|
||||||
|
vsnprintf(buf, char_cnt_without_zero+1, format, argp_copy);
|
||||||
|
va_end(argp_copy);
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
1
misc.h
Normal file
1
misc.h
Normal file
@@ -0,0 +1 @@
|
|||||||
|
char* dc_mprintf (const char* format, ...); /* The result must be free()'d. */
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
# Seeds for failure cases proptest has generated in the past. It is
|
|
||||||
# automatically read and these particular cases re-run before any
|
|
||||||
# novel cases are generated.
|
|
||||||
#
|
|
||||||
# It is recommended to check this file in to source control so that
|
|
||||||
# everyone who runs the test benefits from these saved cases.
|
|
||||||
cc 679506fe9ac59df773f8cfa800fdab5f0a32fe49d2ab370394000a1aa5bc2a72 # shrinks to buf = "%0A"
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
# Seeds for failure cases proptest has generated in the past. It is
|
|
||||||
# automatically read and these particular cases re-run before any
|
|
||||||
# novel cases are generated.
|
|
||||||
#
|
|
||||||
# It is recommended to check this file in to source control so that
|
|
||||||
# everyone who runs the test benefits from these saved cases.
|
|
||||||
cc c310754465ee0261807b96fa9bcc4861ff9aa286e94667524b5960c69f9b6620 # shrinks to buf = "", approx_chars = 0, do_unwrap = false
|
|
||||||
cc 5fd8d730b0a9cdf7308ce58818ca9aefc0255c9ba2a0878944fc48d43a67315b # shrinks to buf = "𑒀ὐ¢🜀\u{1e01b}A a🟠", approx_chars = 0, do_unwrap = false
|
|
||||||
cc c6a0029a54137a4b9efc9ef2ea6d9a7dd1d60d1c937bb472b66a174618ba8013 # shrinks to buf = "𐠈0Aᝮa𫝀®!ꫛa¡0A𐢧00𐹠®A 丽ⷐએ ", approx_chars = 0, do_unwrap = false
|
|
||||||
@@ -4,12 +4,6 @@
|
|||||||
- introduce automatic versioning via setuptools_scm,
|
- introduce automatic versioning via setuptools_scm,
|
||||||
based on py-X.Y.Z tags
|
based on py-X.Y.Z tags
|
||||||
|
|
||||||
- integrate latest DCC core-rust with dc_close() fixes
|
|
||||||
|
|
||||||
- provide a account.shutdown() method and improve termination
|
|
||||||
logic also in tests. also fixes output-clubbering during
|
|
||||||
test runs.
|
|
||||||
|
|
||||||
|
|
||||||
0.600.0
|
0.600.0
|
||||||
---------
|
---------
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ without any "build-from-source" steps.
|
|||||||
1. `Install virtualenv <https://virtualenv.pypa.io/en/stable/installation/>`_,
|
1. `Install virtualenv <https://virtualenv.pypa.io/en/stable/installation/>`_,
|
||||||
then create a fresh python environment and activate it in your shell::
|
then create a fresh python environment and activate it in your shell::
|
||||||
|
|
||||||
virtualenv venv # or: python -m venv
|
virtualenv -p python3 venv
|
||||||
source venv/bin/activate
|
source venv/bin/activate
|
||||||
|
|
||||||
Afterwards, invoking ``python`` or ``pip install`` will only
|
Afterwards, invoking ``python`` or ``pip install`` will only
|
||||||
@@ -39,12 +39,6 @@ and push them to a python package index. To install the latest github ``master``
|
|||||||
|
|
||||||
pip install -i https://m.devpi.net/dc/master deltachat
|
pip install -i https://m.devpi.net/dc/master deltachat
|
||||||
|
|
||||||
.. note::
|
|
||||||
|
|
||||||
If you can help to automate the building of wheels for Mac or Windows,
|
|
||||||
that'd be much appreciated! please then get
|
|
||||||
`in contact with us <https://delta.chat/en/contribute>`_.
|
|
||||||
|
|
||||||
|
|
||||||
Installing bindings from source
|
Installing bindings from source
|
||||||
===============================
|
===============================
|
||||||
@@ -54,55 +48,34 @@ to core deltachat library::
|
|||||||
|
|
||||||
git clone https://github.com/deltachat/deltachat-core-rust
|
git clone https://github.com/deltachat/deltachat-core-rust
|
||||||
cd deltachat-core-rust
|
cd deltachat-core-rust
|
||||||
|
cargo build -p deltachat_ffi --release
|
||||||
|
|
||||||
|
This will result in a ``libdeltachat.so`` and ``libdeltachat.a`` files
|
||||||
|
in the ``target/release`` directory. These files are needed for
|
||||||
|
creating the python bindings for deltachat::
|
||||||
|
|
||||||
cd python
|
cd python
|
||||||
|
DCC_RS_DEV=`pwd`/.. pip install -e .
|
||||||
|
|
||||||
If you don't have one active, create and activate a python "virtualenv":
|
Now test if the bindings find the correct library::
|
||||||
|
|
||||||
python virtualenv venv # or python -m venv
|
python -c 'import deltachat ; print(deltachat.__version__)'
|
||||||
source venv/bin/activate
|
|
||||||
|
|
||||||
Afterwards ``which python`` tells you that it comes out of the "venv"
|
This should print your deltachat bindings version.
|
||||||
directory that contains all python install artifacts. Let's first
|
|
||||||
install test tools::
|
|
||||||
|
|
||||||
pip install pytest pytest-timeout pytest-rerunfailures requests
|
|
||||||
|
|
||||||
then cargo-build and install the deltachat bindings::
|
|
||||||
|
|
||||||
python install_python_bindings.py
|
|
||||||
|
|
||||||
The bindings will be installed in release mode but with debug symbols.
|
|
||||||
The release mode is necessary because some tests generate RSA keys
|
|
||||||
which is prohibitively slow in debug mode.
|
|
||||||
|
|
||||||
After successful binding installation you can finally run the tests::
|
|
||||||
|
|
||||||
pytest -v tests
|
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
|
|
||||||
Some tests are sometimes failing/hanging because of
|
If you can help to automate the building of wheels for Mac or Windows,
|
||||||
https://github.com/deltachat/deltachat-core-rust/issues/331
|
that'd be much appreciated! please then get
|
||||||
and
|
`in contact with us <https://delta.chat/en/contribute>`_.
|
||||||
https://github.com/deltachat/deltachat-core-rust/issues/326
|
|
||||||
|
|
||||||
|
Using a system-installed deltachat-core-rust
|
||||||
|
--------------------------------------------
|
||||||
|
|
||||||
running "live" tests (experimental)
|
When calling ``pip`` without specifying the ``DCC_RS_DEV`` environment
|
||||||
-----------------------------------
|
variable cffi will try to use a ``deltachat.h`` from a system location
|
||||||
|
like ``/usr/local/include`` and will try to dynamically link against a
|
||||||
If you want to run "liveconfig" functional tests you can set
|
``libdeltachat.so`` in a similar location (e.g. ``/usr/local/lib``).
|
||||||
``DCC_PY_LIVECONFIG`` to:
|
|
||||||
|
|
||||||
- a particular https-url that you can ask for from the delta
|
|
||||||
chat devs.
|
|
||||||
|
|
||||||
- or the path of a file that contains two lines, each describing
|
|
||||||
via "addr=... mail_pwd=..." a test account login that will
|
|
||||||
be used for the live tests.
|
|
||||||
|
|
||||||
With ``DCC_PY_LIVECONFIG`` set pytest invocations will use real
|
|
||||||
e-mail accounts and run through all functional "liveconfig" tests.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Code examples
|
Code examples
|
||||||
@@ -111,34 +84,68 @@ Code examples
|
|||||||
You may look at `examples <https://py.delta.chat/examples.html>`_.
|
You may look at `examples <https://py.delta.chat/examples.html>`_.
|
||||||
|
|
||||||
|
|
||||||
|
Running tests
|
||||||
|
=============
|
||||||
|
|
||||||
|
Get a checkout of the `deltachat-core-rust github repository`_ and type::
|
||||||
|
|
||||||
|
pip install tox
|
||||||
|
./run-integration-tests.sh
|
||||||
|
|
||||||
|
If you want to run functional tests with real
|
||||||
|
e-mail test accounts, generate a "liveconfig" file where each
|
||||||
|
lines contains test account settings, for example::
|
||||||
|
|
||||||
|
# 'liveconfig' file specifying imap/smtp accounts
|
||||||
|
addr=some-email@example.org mail_pw=password
|
||||||
|
addr=other-email@example.org mail_pw=otherpassword
|
||||||
|
|
||||||
|
The "keyword=value" style allows to specify any
|
||||||
|
`deltachat account config setting <https://c.delta.chat/classdc__context__t.html#aff3b894f6cfca46cab5248fdffdf083d>`_ so you can also specify smtp or imap servers, ports, ssl modes etc.
|
||||||
|
Typically DC's automatic configuration allows to not specify these settings.
|
||||||
|
|
||||||
|
The ``run-integration-tests.sh`` script will automatically use
|
||||||
|
``python/liveconfig`` if it exists, to manually run tests with this
|
||||||
|
``liveconfig`` file use::
|
||||||
|
|
||||||
|
tox -- --liveconfig liveconfig
|
||||||
|
|
||||||
|
|
||||||
.. _`deltachat-core-rust github repository`: https://github.com/deltachat/deltachat-core-rust
|
.. _`deltachat-core-rust github repository`: https://github.com/deltachat/deltachat-core-rust
|
||||||
.. _`deltachat-core`: https://github.com/deltachat/deltachat-core-rust
|
.. _`deltachat-core`: https://github.com/deltachat/deltachat-core-rust
|
||||||
|
|
||||||
|
Running test using a debug build
|
||||||
|
--------------------------------
|
||||||
|
|
||||||
|
If you need to examine e.g. a coredump you may want to run the tests
|
||||||
|
using a debug build::
|
||||||
|
|
||||||
|
DCC_RS_TARGET=debug ./run-integration-tests.sh -e py37 -- -x -v -k failing_test
|
||||||
|
|
||||||
|
|
||||||
Building manylinux1 wheels
|
Building manylinux1 wheels
|
||||||
==========================
|
==========================
|
||||||
|
|
||||||
.. note::
|
|
||||||
|
|
||||||
This section may not fully work.
|
|
||||||
|
|
||||||
Building portable manylinux1 wheels which come with libdeltachat.so
|
Building portable manylinux1 wheels which come with libdeltachat.so
|
||||||
and all it's dependencies is easy using the provided docker tooling.
|
and all it's dependencies is easy using the provided docker tooling.
|
||||||
|
|
||||||
using docker pull / premade images
|
using docker pull / premade images
|
||||||
------------------------------------
|
------------------------------------
|
||||||
|
|
||||||
We publish a build environment under the ``deltachat/coredeps`` tag so
|
We publish a build environment under the ``deltachat/wheel`` tag so
|
||||||
that you can pull it from the ``hub.docker.com`` site's "deltachat"
|
that you can pull it from the ``hub.docker.com`` site's "deltachat"
|
||||||
organization::
|
organization::
|
||||||
|
|
||||||
$ docker pull deltachat/coredeps
|
$ docker pull deltachat/wheel
|
||||||
|
|
||||||
This docker image can be used to run tests and build Python wheels for all interpreters::
|
The ``deltachat/wheel`` image can be used to build both libdeltachat.so
|
||||||
|
and the Python wheels::
|
||||||
|
|
||||||
$ bash ci_scripts/ci_run.sh
|
$ docker run --rm -it -v $(pwd):/io/ deltachat/wheel /io/python/wheelbuilder/build-wheels.sh
|
||||||
|
|
||||||
This command runs tests and build-wheel scripts in a docker container.
|
This command runs a script within the image, after mounting ``$(pwd)`` as ``/io`` within
|
||||||
|
the docker image. The script is specified as a path within the docker image's filesystem.
|
||||||
|
The resulting wheel files will be in ``python/wheelhouse``.
|
||||||
|
|
||||||
|
|
||||||
Optionally build your own docker image
|
Optionally build your own docker image
|
||||||
@@ -147,10 +154,10 @@ Optionally build your own docker image
|
|||||||
If you want to build your own custom docker image you can do this::
|
If you want to build your own custom docker image you can do this::
|
||||||
|
|
||||||
$ cd deltachat-core # cd to deltachat-core checkout directory
|
$ cd deltachat-core # cd to deltachat-core checkout directory
|
||||||
$ docker build -t deltachat/coredeps ci_scripts/docker_coredeps
|
$ docker build -t deltachat/wheel python/wheelbuilder/
|
||||||
|
|
||||||
This will use the ``ci_scripts/docker_coredeps/Dockerfile`` to build
|
This will use the ``python/wheelbuilder/Dockerfile`` to build
|
||||||
up docker image called ``deltachat/coredeps``. You can afterwards
|
up docker image called ``deltachat/wheel``. You can afterwards
|
||||||
find it with::
|
find it with::
|
||||||
|
|
||||||
$ docker images
|
$ docker images
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ high level API reference
|
|||||||
- :class:`deltachat.chatting.Contact`
|
- :class:`deltachat.chatting.Contact`
|
||||||
- :class:`deltachat.chatting.Chat`
|
- :class:`deltachat.chatting.Chat`
|
||||||
- :class:`deltachat.message.Message`
|
- :class:`deltachat.message.Message`
|
||||||
|
- :class:`deltachat.message.MessageType`
|
||||||
|
- :class:`deltachat.message.MessageState`
|
||||||
|
|
||||||
Account
|
Account
|
||||||
-------
|
-------
|
||||||
@@ -37,3 +39,16 @@ Message
|
|||||||
.. autoclass:: deltachat.message.Message
|
.. autoclass:: deltachat.message.Message
|
||||||
:members:
|
:members:
|
||||||
|
|
||||||
|
MessageType
|
||||||
|
------------
|
||||||
|
|
||||||
|
.. autoclass:: deltachat.message.MessageType
|
||||||
|
:members:
|
||||||
|
|
||||||
|
MessageState
|
||||||
|
------------
|
||||||
|
|
||||||
|
.. autoclass:: deltachat.message.MessageState
|
||||||
|
:members:
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -288,6 +288,10 @@ intersphinx_mapping = {'http://docs.python.org/': None}
|
|||||||
autodoc_member_order = "bysource"
|
autodoc_member_order = "bysource"
|
||||||
# always document __init__ functions
|
# always document __init__ functions
|
||||||
def skip(app, what, name, obj, skip, options):
|
def skip(app, what, name, obj, skip, options):
|
||||||
|
import attr
|
||||||
|
if name == "__init__":
|
||||||
|
if not hasattr(obj.im_class, "__attrs_attrs__"):
|
||||||
|
return False
|
||||||
return skip
|
return skip
|
||||||
|
|
||||||
def setup(app):
|
def setup(app):
|
||||||
|
|||||||
@@ -8,7 +8,8 @@ Playing around on the commandline
|
|||||||
----------------------------------
|
----------------------------------
|
||||||
|
|
||||||
Once you have :doc:`installed deltachat bindings <install>`
|
Once you have :doc:`installed deltachat bindings <install>`
|
||||||
you can start playing from the python interpreter commandline.
|
you can start playing from the python interpreter commandline::
|
||||||
|
|
||||||
For example you can type ``python`` and then::
|
For example you can type ``python`` and then::
|
||||||
|
|
||||||
# instantiate and configure deltachat account
|
# instantiate and configure deltachat account
|
||||||
@@ -22,7 +23,7 @@ For example you can type ``python`` and then::
|
|||||||
# create a contact and send a message
|
# create a contact and send a message
|
||||||
contact = ac.create_contact("someother@email.address")
|
contact = ac.create_contact("someother@email.address")
|
||||||
chat = ac.create_chat_by_contact(contact)
|
chat = ac.create_chat_by_contact(contact)
|
||||||
chat.send_text("hi from the python interpreter command line")
|
chat.send_text_message("hi from the python interpreter command line")
|
||||||
|
|
||||||
Checkout our :doc:`api` for the various high-level things you can do
|
Checkout our :doc:`api` for the various high-level things you can do
|
||||||
to send/receive messages, create contacts and chats.
|
to send/receive messages, create contacts and chats.
|
||||||
|
|||||||
7
python/install_py_bindings.sh
Executable file
7
python/install_py_bindings.sh
Executable file
@@ -0,0 +1,7 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -ex
|
||||||
|
|
||||||
|
cargo build -p deltachat_ffi
|
||||||
|
rm -rf build/ src/deltachat/*.so
|
||||||
|
DCC_RS_DEV=`pwd`/.. pip install -e .
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
|
|
||||||
"""
|
|
||||||
setup a python binding development in-place install with cargo debug symbols.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import os
|
|
||||||
import subprocess
|
|
||||||
import os
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
os.environ["DCC_RS_TARGET"] = target = "release"
|
|
||||||
if "DCC_RS_DEV" not in os.environ:
|
|
||||||
dn = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
|
||||||
os.environ["DCC_RS_DEV"] = dn
|
|
||||||
|
|
||||||
os.environ["RUSTFLAGS"] = "-g"
|
|
||||||
subprocess.check_call([
|
|
||||||
"cargo", "build", "-p", "deltachat_ffi", "--" + target
|
|
||||||
])
|
|
||||||
subprocess.check_call("rm -rf build/ src/deltachat/*.so" , shell=True)
|
|
||||||
|
|
||||||
subprocess.check_call([
|
|
||||||
"pip", "install", "-e", "."
|
|
||||||
])
|
|
||||||
@@ -17,7 +17,7 @@ def main():
|
|||||||
description='Python bindings for the Delta Chat Core library using CFFI against the Rust-implemented libdeltachat',
|
description='Python bindings for the Delta Chat Core library using CFFI against the Rust-implemented libdeltachat',
|
||||||
long_description=long_description,
|
long_description=long_description,
|
||||||
author='holger krekel, Floris Bruynooghe, Bjoern Petersen and contributors',
|
author='holger krekel, Floris Bruynooghe, Bjoern Petersen and contributors',
|
||||||
install_requires=['cffi>=1.0.0', 'six'],
|
install_requires=['cffi>=1.0.0', 'attrs', 'six'],
|
||||||
packages=setuptools.find_packages('src'),
|
packages=setuptools.find_packages('src'),
|
||||||
package_dir={'': 'src'},
|
package_dir={'': 'src'},
|
||||||
cffi_modules=['src/deltachat/_build.py:ffibuilder'],
|
cffi_modules=['src/deltachat/_build.py:ffibuilder'],
|
||||||
|
|||||||
@@ -34,14 +34,13 @@ def py_dc_callback(ctx, evt, data1, data2):
|
|||||||
if data1 and event_sig_types & 1:
|
if data1 and event_sig_types & 1:
|
||||||
data1 = ffi.string(ffi.cast('char*', data1)).decode("utf8")
|
data1 = ffi.string(ffi.cast('char*', data1)).decode("utf8")
|
||||||
if data2 and event_sig_types & 2:
|
if data2 and event_sig_types & 2:
|
||||||
data2 = ffi.string(ffi.cast('char*', data2)).decode("utf8")
|
|
||||||
try:
|
try:
|
||||||
if isinstance(data2, bytes):
|
data2 = ffi.string(ffi.cast('char*', data2)).decode("utf8")
|
||||||
data2 = data2.decode("utf8")
|
|
||||||
except UnicodeDecodeError:
|
except UnicodeDecodeError:
|
||||||
# XXX ignoring the decode error is not quite correct but for now
|
# XXX ignoring this error is not quite correct but for now
|
||||||
# i don't want to hunt down encoding problems in the c lib
|
# i don't want to hunt down encoding problems in the c lib
|
||||||
pass
|
data2 = ffi.string(ffi.cast('char*', data2))
|
||||||
|
|
||||||
try:
|
try:
|
||||||
ret = callback(ctx, evt_name, data1, data2)
|
ret = callback(ctx, evt_name, data1, data2)
|
||||||
if ret is None:
|
if ret is None:
|
||||||
@@ -62,10 +61,7 @@ def set_context_callback(dc_context, func):
|
|||||||
|
|
||||||
|
|
||||||
def clear_context_callback(dc_context):
|
def clear_context_callback(dc_context):
|
||||||
try:
|
_DC_CALLBACK_MAP.pop(dc_context, None)
|
||||||
_DC_CALLBACK_MAP.pop(dc_context, None)
|
|
||||||
except AttributeError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def get_dc_event_name(integer, _DC_EVENTNAME_MAP={}):
|
def get_dc_event_name(integer, _DC_EVENTNAME_MAP={}):
|
||||||
|
|||||||
@@ -6,15 +6,10 @@ import platform
|
|||||||
import os
|
import os
|
||||||
import cffi
|
import cffi
|
||||||
import shutil
|
import shutil
|
||||||
from os.path import dirname as dn
|
|
||||||
from os.path import abspath
|
|
||||||
|
|
||||||
|
|
||||||
def ffibuilder():
|
def ffibuilder():
|
||||||
projdir = os.environ.get('DCC_RS_DEV')
|
projdir = os.environ.get('DCC_RS_DEV')
|
||||||
if not projdir:
|
|
||||||
p = dn(dn(dn(dn(abspath(__file__)))))
|
|
||||||
projdir = os.environ["DCC_RS_DEV"] = p
|
|
||||||
target = os.environ.get('DCC_RS_TARGET', 'release')
|
target = os.environ.get('DCC_RS_TARGET', 'release')
|
||||||
if projdir:
|
if projdir:
|
||||||
if platform.system() == 'Darwin':
|
if platform.system() == 'Darwin':
|
||||||
@@ -30,13 +25,11 @@ def ffibuilder():
|
|||||||
else:
|
else:
|
||||||
raise NotImplementedError("Compilation not supported yet on Windows, can you help?")
|
raise NotImplementedError("Compilation not supported yet on Windows, can you help?")
|
||||||
objs = [os.path.join(projdir, 'target', target, 'libdeltachat.a')]
|
objs = [os.path.join(projdir, 'target', target, 'libdeltachat.a')]
|
||||||
assert os.path.exists(objs[0]), objs
|
|
||||||
incs = [os.path.join(projdir, 'deltachat-ffi')]
|
incs = [os.path.join(projdir, 'deltachat-ffi')]
|
||||||
else:
|
else:
|
||||||
libs = ['deltachat']
|
libs = ['deltachat']
|
||||||
objs = []
|
objs = []
|
||||||
incs = []
|
incs = []
|
||||||
extra_link_args = []
|
|
||||||
builder = cffi.FFI()
|
builder = cffi.FFI()
|
||||||
builder.set_source(
|
builder.set_source(
|
||||||
'deltachat.capi',
|
'deltachat.capi',
|
||||||
@@ -76,8 +69,8 @@ def ffibuilder():
|
|||||||
distutils.sysconfig.customize_compiler(cc)
|
distutils.sysconfig.customize_compiler(cc)
|
||||||
tmpdir = tempfile.mkdtemp()
|
tmpdir = tempfile.mkdtemp()
|
||||||
try:
|
try:
|
||||||
src_name = os.path.join(tmpdir, "include.h")
|
src_name = os.path.join(tmpdir, "prep.h")
|
||||||
dst_name = os.path.join(tmpdir, "expanded.h")
|
dst_name = os.path.join(tmpdir, "prep2.c")
|
||||||
with open(src_name, "w") as src_fp:
|
with open(src_name, "w") as src_fp:
|
||||||
src_fp.write('#include <deltachat.h>')
|
src_fp.write('#include <deltachat.h>')
|
||||||
cc.preprocess(source=src_name,
|
cc.preprocess(source=src_name,
|
||||||
|
|||||||
@@ -7,14 +7,14 @@ import re
|
|||||||
import time
|
import time
|
||||||
from array import array
|
from array import array
|
||||||
try:
|
try:
|
||||||
from queue import Queue, Empty
|
from queue import Queue
|
||||||
except ImportError:
|
except ImportError:
|
||||||
from Queue import Queue, Empty
|
from Queue import Queue
|
||||||
|
|
||||||
import deltachat
|
import deltachat
|
||||||
from . import const
|
from . import const
|
||||||
from .capi import ffi, lib
|
from .capi import ffi, lib
|
||||||
from .cutil import as_dc_charpointer, from_dc_charpointer, iter_array, DCLot
|
from .cutil import as_dc_charpointer, from_dc_charpointer, iter_array
|
||||||
from .chatting import Contact, Chat, Message
|
from .chatting import Contact, Chat, Message
|
||||||
|
|
||||||
|
|
||||||
@@ -36,17 +36,14 @@ class Account(object):
|
|||||||
lib.dc_context_new(lib.py_dc_callback, ffi.NULL, ffi.NULL),
|
lib.dc_context_new(lib.py_dc_callback, ffi.NULL, ffi.NULL),
|
||||||
_destroy_dc_context,
|
_destroy_dc_context,
|
||||||
)
|
)
|
||||||
if eventlogging:
|
|
||||||
self._evlogger = EventLogger(self._dc_context, logid)
|
|
||||||
deltachat.set_context_callback(self._dc_context, self._process_event)
|
|
||||||
self._threads = IOThreads(self._dc_context, self._evlogger._log_event)
|
|
||||||
else:
|
|
||||||
self._threads = IOThreads(self._dc_context)
|
|
||||||
|
|
||||||
if hasattr(db_path, "encode"):
|
if hasattr(db_path, "encode"):
|
||||||
db_path = db_path.encode("utf8")
|
db_path = db_path.encode("utf8")
|
||||||
if not lib.dc_open(self._dc_context, db_path, ffi.NULL):
|
if not lib.dc_open(self._dc_context, db_path, ffi.NULL):
|
||||||
raise ValueError("Could not dc_open: {}".format(db_path))
|
raise ValueError("Could not dc_open: {}".format(db_path))
|
||||||
|
if eventlogging:
|
||||||
|
self._evlogger = EventLogger(self._dc_context, logid)
|
||||||
|
deltachat.set_context_callback(self._dc_context, self._process_event)
|
||||||
|
self._threads = IOThreads(self._dc_context)
|
||||||
self._configkeys = self.get_config("sys.config_keys").split()
|
self._configkeys = self.get_config("sys.config_keys").split()
|
||||||
self._imex_completed = threading.Event()
|
self._imex_completed = threading.Event()
|
||||||
|
|
||||||
@@ -142,6 +139,15 @@ class Account(object):
|
|||||||
self.check_is_configured()
|
self.check_is_configured()
|
||||||
return Contact(self._dc_context, const.DC_CONTACT_ID_SELF)
|
return Contact(self._dc_context, const.DC_CONTACT_ID_SELF)
|
||||||
|
|
||||||
|
def create_message(self, view_type):
|
||||||
|
""" create a new non persistent message.
|
||||||
|
|
||||||
|
:param view_type: a string specifying "text", "video",
|
||||||
|
"image", "audio" or "file".
|
||||||
|
:returns: :class:`deltachat.message.Message` instance.
|
||||||
|
"""
|
||||||
|
return Message.new(self._dc_context, view_type)
|
||||||
|
|
||||||
def create_contact(self, email, name=None):
|
def create_contact(self, email, name=None):
|
||||||
""" create a (new) Contact. If there already is a Contact
|
""" create a (new) Contact. If there already is a Contact
|
||||||
with that e-mail address, it is unblocked and its name is
|
with that e-mail address, it is unblocked and its name is
|
||||||
@@ -157,17 +163,6 @@ class Account(object):
|
|||||||
assert contact_id > const.DC_CHAT_ID_LAST_SPECIAL
|
assert contact_id > const.DC_CHAT_ID_LAST_SPECIAL
|
||||||
return Contact(self._dc_context, contact_id)
|
return Contact(self._dc_context, contact_id)
|
||||||
|
|
||||||
def delete_contact(self, contact):
|
|
||||||
""" delete a Contact.
|
|
||||||
|
|
||||||
:param contact: contact object obtained
|
|
||||||
:returns: True if deletion succeeded (contact was deleted)
|
|
||||||
"""
|
|
||||||
contact_id = contact.id
|
|
||||||
assert contact._dc_context == self._dc_context
|
|
||||||
assert contact_id > const.DC_CHAT_ID_LAST_SPECIAL
|
|
||||||
return bool(lib.dc_delete_contact(self._dc_context, contact_id))
|
|
||||||
|
|
||||||
def get_contacts(self, query=None, with_self=False, only_verified=False):
|
def get_contacts(self, query=None, with_self=False, only_verified=False):
|
||||||
""" get a (filtered) list of contacts.
|
""" get a (filtered) list of contacts.
|
||||||
|
|
||||||
@@ -175,7 +170,7 @@ class Account(object):
|
|||||||
whose name or e-mail matches query.
|
whose name or e-mail matches query.
|
||||||
:param only_verified: if true only return verified contacts.
|
:param only_verified: if true only return verified contacts.
|
||||||
:param with_self: if true the self-contact is also returned.
|
:param with_self: if true the self-contact is also returned.
|
||||||
:returns: list of :class:`deltachat.chatting.Contact` objects.
|
:returns: list of :class:`deltachat.message.Message` objects.
|
||||||
"""
|
"""
|
||||||
flags = 0
|
flags = 0
|
||||||
query = as_dc_charpointer(query)
|
query = as_dc_charpointer(query)
|
||||||
@@ -203,7 +198,7 @@ class Account(object):
|
|||||||
assert isinstance(contact, int)
|
assert isinstance(contact, int)
|
||||||
contact_id = contact
|
contact_id = contact
|
||||||
chat_id = lib.dc_create_chat_by_contact_id(self._dc_context, contact_id)
|
chat_id = lib.dc_create_chat_by_contact_id(self._dc_context, contact_id)
|
||||||
return Chat(self, chat_id)
|
return Chat(self._dc_context, chat_id)
|
||||||
|
|
||||||
def create_chat_by_message(self, message):
|
def create_chat_by_message(self, message):
|
||||||
""" create or get an existing chat object for the
|
""" create or get an existing chat object for the
|
||||||
@@ -220,7 +215,7 @@ class Account(object):
|
|||||||
assert isinstance(message, int)
|
assert isinstance(message, int)
|
||||||
msg_id = message
|
msg_id = message
|
||||||
chat_id = lib.dc_create_chat_by_msg_id(self._dc_context, msg_id)
|
chat_id = lib.dc_create_chat_by_msg_id(self._dc_context, msg_id)
|
||||||
return Chat(self, chat_id)
|
return Chat(self._dc_context, chat_id)
|
||||||
|
|
||||||
def create_group_chat(self, name, verified=False):
|
def create_group_chat(self, name, verified=False):
|
||||||
""" create a new group chat object.
|
""" create a new group chat object.
|
||||||
@@ -232,7 +227,7 @@ class Account(object):
|
|||||||
"""
|
"""
|
||||||
bytes_name = name.encode("utf8")
|
bytes_name = name.encode("utf8")
|
||||||
chat_id = lib.dc_create_group_chat(self._dc_context, verified, bytes_name)
|
chat_id = lib.dc_create_group_chat(self._dc_context, verified, bytes_name)
|
||||||
return Chat(self, chat_id)
|
return Chat(self._dc_context, chat_id)
|
||||||
|
|
||||||
def get_chats(self):
|
def get_chats(self):
|
||||||
""" return list of chats.
|
""" return list of chats.
|
||||||
@@ -248,15 +243,15 @@ class Account(object):
|
|||||||
chatlist = []
|
chatlist = []
|
||||||
for i in range(0, lib.dc_chatlist_get_cnt(dc_chatlist)):
|
for i in range(0, lib.dc_chatlist_get_cnt(dc_chatlist)):
|
||||||
chat_id = lib.dc_chatlist_get_chat_id(dc_chatlist, i)
|
chat_id = lib.dc_chatlist_get_chat_id(dc_chatlist, i)
|
||||||
chatlist.append(Chat(self, chat_id))
|
chatlist.append(Chat(self._dc_context, chat_id))
|
||||||
return chatlist
|
return chatlist
|
||||||
|
|
||||||
def get_deaddrop_chat(self):
|
def get_deaddrop_chat(self):
|
||||||
return Chat(self, const.DC_CHAT_ID_DEADDROP)
|
return Chat(self._dc_context, const.DC_CHAT_ID_DEADDROP)
|
||||||
|
|
||||||
def get_message_by_id(self, msg_id):
|
def get_message_by_id(self, msg_id):
|
||||||
""" return Message instance. """
|
""" return Message instance. """
|
||||||
return Message.from_db(self, msg_id)
|
return Message.from_db(self._dc_context, msg_id)
|
||||||
|
|
||||||
def mark_seen_messages(self, messages):
|
def mark_seen_messages(self, messages):
|
||||||
""" mark the given set of messages as seen.
|
""" mark the given set of messages as seen.
|
||||||
@@ -328,56 +323,6 @@ class Account(object):
|
|||||||
raise RuntimeError("could not send out autocrypt setup message")
|
raise RuntimeError("could not send out autocrypt setup message")
|
||||||
return from_dc_charpointer(res)
|
return from_dc_charpointer(res)
|
||||||
|
|
||||||
def get_setup_contact_qr(self):
|
|
||||||
""" get/create Setup-Contact QR Code as ascii-string.
|
|
||||||
|
|
||||||
this string needs to be transferred to another DC account
|
|
||||||
in a second channel (typically used by mobiles with QRcode-show + scan UX)
|
|
||||||
where qr_setup_contact(qr) is called.
|
|
||||||
"""
|
|
||||||
res = lib.dc_get_securejoin_qr(self._dc_context, 0)
|
|
||||||
return from_dc_charpointer(res)
|
|
||||||
|
|
||||||
def check_qr(self, qr):
|
|
||||||
""" check qr code and return :class:`ScannedQRCode` instance representing the result"""
|
|
||||||
res = ffi.gc(
|
|
||||||
lib.dc_check_qr(self._dc_context, as_dc_charpointer(qr)),
|
|
||||||
lib.dc_lot_unref
|
|
||||||
)
|
|
||||||
lot = DCLot(res)
|
|
||||||
if lot.state() == const.DC_QR_ERROR:
|
|
||||||
raise ValueError("invalid or unknown QR code: {}".format(lot.text1()))
|
|
||||||
return ScannedQRCode(lot)
|
|
||||||
|
|
||||||
def qr_setup_contact(self, qr):
|
|
||||||
""" setup contact and return a Chat after contact is established.
|
|
||||||
|
|
||||||
Note that this function may block for a long time as messages are exchanged
|
|
||||||
with the emitter of the QR code. On success a :class:`deltachat.chatting.Chat` instance
|
|
||||||
is returned.
|
|
||||||
:param qr: valid "setup contact" QR code (all other QR codes will result in an exception)
|
|
||||||
"""
|
|
||||||
assert self.check_qr(qr).is_ask_verifycontact()
|
|
||||||
chat_id = lib.dc_join_securejoin(self._dc_context, as_dc_charpointer(qr))
|
|
||||||
if chat_id == 0:
|
|
||||||
raise ValueError("could not setup secure contact")
|
|
||||||
return Chat(self, chat_id)
|
|
||||||
|
|
||||||
def qr_join_chat(self, qr):
|
|
||||||
""" join a chat group through a QR code.
|
|
||||||
|
|
||||||
Note that this function may block for a long time as messages are exchanged
|
|
||||||
with the emitter of the QR code. On success a :class:`deltachat.chatting.Chat` instance
|
|
||||||
is returned which is the chat that we just joined.
|
|
||||||
|
|
||||||
:param qr: valid "join-group" QR code (all other QR codes will result in an exception)
|
|
||||||
"""
|
|
||||||
assert self.check_qr(qr).is_ask_verifygroup()
|
|
||||||
chat_id = lib.dc_join_securejoin(self._dc_context, as_dc_charpointer(qr))
|
|
||||||
if chat_id == 0:
|
|
||||||
raise ValueError("could not join group")
|
|
||||||
return Chat(self, chat_id)
|
|
||||||
|
|
||||||
def start_threads(self):
|
def start_threads(self):
|
||||||
""" start IMAP/SMTP threads (and configure account if it hasn't happened).
|
""" start IMAP/SMTP threads (and configure account if it hasn't happened).
|
||||||
|
|
||||||
@@ -395,9 +340,8 @@ class Account(object):
|
|||||||
|
|
||||||
def shutdown(self, wait=True):
|
def shutdown(self, wait=True):
|
||||||
""" stop threads and close and remove underlying dc_context and callbacks. """
|
""" stop threads and close and remove underlying dc_context and callbacks. """
|
||||||
if hasattr(self, "_dc_context") and hasattr(self, "_threads"):
|
if hasattr(self, "_dc_context"):
|
||||||
# print("SHUTDOWN", self)
|
self.stop_threads(wait=False) # to interrupt idle and tell python threads to stop
|
||||||
self.stop_threads(wait=False)
|
|
||||||
lib.dc_close(self._dc_context)
|
lib.dc_close(self._dc_context)
|
||||||
self.stop_threads(wait=wait) # to wait for threads
|
self.stop_threads(wait=wait) # to wait for threads
|
||||||
deltachat.clear_context_callback(self._dc_context)
|
deltachat.clear_context_callback(self._dc_context)
|
||||||
@@ -418,11 +362,10 @@ class Account(object):
|
|||||||
|
|
||||||
|
|
||||||
class IOThreads:
|
class IOThreads:
|
||||||
def __init__(self, dc_context, log_event=lambda *args: None):
|
def __init__(self, dc_context):
|
||||||
self._dc_context = dc_context
|
self._dc_context = dc_context
|
||||||
self._thread_quitflag = False
|
self._thread_quitflag = False
|
||||||
self._name2thread = {}
|
self._name2thread = {}
|
||||||
self._log_event = log_event
|
|
||||||
|
|
||||||
def is_started(self):
|
def is_started(self):
|
||||||
return len(self._name2thread) > 0
|
return len(self._name2thread) > 0
|
||||||
@@ -448,19 +391,17 @@ class IOThreads:
|
|||||||
thread.join()
|
thread.join()
|
||||||
|
|
||||||
def imap_thread_run(self):
|
def imap_thread_run(self):
|
||||||
self._log_event("py-bindings-info", 0, "IMAP THREAD START")
|
|
||||||
while not self._thread_quitflag:
|
while not self._thread_quitflag:
|
||||||
lib.dc_perform_imap_jobs(self._dc_context)
|
lib.dc_perform_imap_jobs(self._dc_context)
|
||||||
lib.dc_perform_imap_fetch(self._dc_context)
|
lib.dc_perform_imap_fetch(self._dc_context)
|
||||||
lib.dc_perform_imap_idle(self._dc_context)
|
lib.dc_perform_imap_idle(self._dc_context)
|
||||||
self._log_event("py-bindings-info", 0, "IMAP THREAD FINISHED")
|
print("IMAP_THREAD finished")
|
||||||
|
|
||||||
def smtp_thread_run(self):
|
def smtp_thread_run(self):
|
||||||
self._log_event("py-bindings-info", 0, "SMTP THREAD START")
|
|
||||||
while not self._thread_quitflag:
|
while not self._thread_quitflag:
|
||||||
lib.dc_perform_smtp_jobs(self._dc_context)
|
lib.dc_perform_smtp_jobs(self._dc_context)
|
||||||
lib.dc_perform_smtp_idle(self._dc_context)
|
lib.dc_perform_smtp_idle(self._dc_context)
|
||||||
self._log_event("py-bindings-info", 0, "SMTP THREAD FINISHED")
|
print("SMTP_THREAD finished")
|
||||||
|
|
||||||
|
|
||||||
class EventLogger:
|
class EventLogger:
|
||||||
@@ -483,10 +424,6 @@ class EventLogger:
|
|||||||
def set_timeout(self, timeout):
|
def set_timeout(self, timeout):
|
||||||
self._timeout = timeout
|
self._timeout = timeout
|
||||||
|
|
||||||
def consume_events(self, check_error=True):
|
|
||||||
while not self._event_queue.empty():
|
|
||||||
self.get()
|
|
||||||
|
|
||||||
def get(self, timeout=None, check_error=True):
|
def get(self, timeout=None, check_error=True):
|
||||||
timeout = timeout or self._timeout
|
timeout = timeout or self._timeout
|
||||||
ev = self._event_queue.get(timeout=timeout)
|
ev = self._event_queue.get(timeout=timeout)
|
||||||
@@ -494,17 +431,6 @@ class EventLogger:
|
|||||||
raise ValueError("{}({!r},{!r})".format(*ev))
|
raise ValueError("{}({!r},{!r})".format(*ev))
|
||||||
return ev
|
return ev
|
||||||
|
|
||||||
def ensure_event_not_queued(self, event_name_regex):
|
|
||||||
__tracebackhide__ = True
|
|
||||||
rex = re.compile("(?:{}).*".format(event_name_regex))
|
|
||||||
while 1:
|
|
||||||
try:
|
|
||||||
ev = self._event_queue.get(False)
|
|
||||||
except Empty:
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
assert not rex.match(ev[0]), "event found {}".format(ev)
|
|
||||||
|
|
||||||
def get_matching(self, event_name_regex, check_error=True):
|
def get_matching(self, event_name_regex, check_error=True):
|
||||||
self._log("-- waiting for event with regex: {} --".format(event_name_regex))
|
self._log("-- waiting for event with regex: {} --".format(event_name_regex))
|
||||||
rex = re.compile("(?:{}).*".format(event_name_regex))
|
rex = re.compile("(?:{}).*".format(event_name_regex))
|
||||||
@@ -546,18 +472,3 @@ def _destroy_dc_context(dc_context, dc_context_unref=lib.dc_context_unref):
|
|||||||
# we are deep into Python Interpreter shutdown,
|
# we are deep into Python Interpreter shutdown,
|
||||||
# so no need to clear the callback context mapping.
|
# so no need to clear the callback context mapping.
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class ScannedQRCode:
|
|
||||||
def __init__(self, dc_lot):
|
|
||||||
self._dc_lot = dc_lot
|
|
||||||
|
|
||||||
def is_ask_verifycontact(self):
|
|
||||||
return self._dc_lot.state() == const.DC_QR_ASK_VERIFYCONTACT
|
|
||||||
|
|
||||||
def is_ask_verifygroup(self):
|
|
||||||
return self._dc_lot.state() == const.DC_QR_ASK_VERIFYGROUP
|
|
||||||
|
|
||||||
@property
|
|
||||||
def contact_id(self):
|
|
||||||
return self._dc_lot.id()
|
|
||||||
|
|||||||
@@ -1,31 +1,24 @@
|
|||||||
""" chatting related objects: Contact, Chat, Message. """
|
""" chatting related objects: Contact, Chat, Message. """
|
||||||
|
|
||||||
import mimetypes
|
|
||||||
import os
|
import os
|
||||||
|
|
||||||
from . import props
|
from . import props
|
||||||
from .cutil import as_dc_charpointer, from_dc_charpointer, iter_array
|
from .cutil import as_dc_charpointer, from_dc_charpointer, iter_array
|
||||||
from .capi import lib, ffi
|
from .capi import lib, ffi
|
||||||
from . import const
|
from . import const
|
||||||
|
import attr
|
||||||
|
from attr import validators as v
|
||||||
from .message import Message
|
from .message import Message
|
||||||
|
|
||||||
|
|
||||||
|
@attr.s
|
||||||
class Contact(object):
|
class Contact(object):
|
||||||
""" Delta-Chat Contact.
|
""" Delta-Chat Contact.
|
||||||
|
|
||||||
You obtain instances of it through :class:`deltachat.account.Account`.
|
You obtain instances of it through :class:`deltachat.account.Account`.
|
||||||
"""
|
"""
|
||||||
def __init__(self, dc_context, id):
|
_dc_context = attr.ib(validator=v.instance_of(ffi.CData))
|
||||||
self._dc_context = dc_context
|
id = attr.ib(validator=v.instance_of(int))
|
||||||
self.id = id
|
|
||||||
|
|
||||||
def __eq__(self, other):
|
|
||||||
return self._dc_context == other._dc_context and self.id == other.id
|
|
||||||
|
|
||||||
def __ne__(self, other):
|
|
||||||
return not (self == other)
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return "<Contact id={} addr={} dc_context={}>".format(self.id, self.addr, self._dc_context)
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def _dc_contact(self):
|
def _dc_contact(self):
|
||||||
@@ -53,26 +46,14 @@ class Contact(object):
|
|||||||
return lib.dc_contact_is_verified(self._dc_contact)
|
return lib.dc_contact_is_verified(self._dc_contact)
|
||||||
|
|
||||||
|
|
||||||
|
@attr.s
|
||||||
class Chat(object):
|
class Chat(object):
|
||||||
""" Chat object which manages members and through which you can send and retrieve messages.
|
""" Chat object which manages members and through which you can send and retrieve messages.
|
||||||
|
|
||||||
You obtain instances of it through :class:`deltachat.account.Account`.
|
You obtain instances of it through :class:`deltachat.account.Account`.
|
||||||
"""
|
"""
|
||||||
|
_dc_context = attr.ib(validator=v.instance_of(ffi.CData))
|
||||||
def __init__(self, account, id):
|
id = attr.ib(validator=v.instance_of(int))
|
||||||
self.account = account
|
|
||||||
self._dc_context = account._dc_context
|
|
||||||
self.id = id
|
|
||||||
|
|
||||||
def __eq__(self, other):
|
|
||||||
return self.id == getattr(other, "id", None) and \
|
|
||||||
self._dc_context == getattr(other, "_dc_context", None)
|
|
||||||
|
|
||||||
def __ne__(self, other):
|
|
||||||
return not (self == other)
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return "<Chat id={} name={} dc_context={}>".format(self.id, self.get_name(), self._dc_context)
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def _dc_chat(self):
|
def _dc_chat(self):
|
||||||
@@ -132,16 +113,6 @@ class Chat(object):
|
|||||||
"""
|
"""
|
||||||
return lib.dc_chat_get_type(self._dc_chat)
|
return lib.dc_chat_get_type(self._dc_chat)
|
||||||
|
|
||||||
def get_join_qr(self):
|
|
||||||
""" get/create Join-Group QR Code as ascii-string.
|
|
||||||
|
|
||||||
this string needs to be transferred to another DC account
|
|
||||||
in a second channel (typically used by mobiles with QRcode-show + scan UX)
|
|
||||||
where account.join_with_qrcode(qr) needs to be called.
|
|
||||||
"""
|
|
||||||
res = lib.dc_get_securejoin_qr(self._dc_context, self.id)
|
|
||||||
return from_dc_charpointer(res)
|
|
||||||
|
|
||||||
# ------ chat messaging API ------------------------------
|
# ------ chat messaging API ------------------------------
|
||||||
|
|
||||||
def send_text(self, text):
|
def send_text(self, text):
|
||||||
@@ -155,7 +126,7 @@ class Chat(object):
|
|||||||
msg_id = lib.dc_send_text_msg(self._dc_context, self.id, msg)
|
msg_id = lib.dc_send_text_msg(self._dc_context, self.id, msg)
|
||||||
if msg_id == 0:
|
if msg_id == 0:
|
||||||
raise ValueError("message could not be send, does chat exist?")
|
raise ValueError("message could not be send, does chat exist?")
|
||||||
return Message.from_db(self.account, msg_id)
|
return Message.from_db(self._dc_context, msg_id)
|
||||||
|
|
||||||
def send_file(self, path, mime_type="application/octet-stream"):
|
def send_file(self, path, mime_type="application/octet-stream"):
|
||||||
""" send a file and return the resulting Message instance.
|
""" send a file and return the resulting Message instance.
|
||||||
@@ -165,9 +136,14 @@ class Chat(object):
|
|||||||
:raises ValueError: if message can not be send/chat does not exist.
|
:raises ValueError: if message can not be send/chat does not exist.
|
||||||
:returns: the resulting :class:`deltachat.message.Message` instance
|
:returns: the resulting :class:`deltachat.message.Message` instance
|
||||||
"""
|
"""
|
||||||
msg = self.prepare_message_file(path=path, mime_type=mime_type)
|
path = as_dc_charpointer(path)
|
||||||
self.send_prepared(msg)
|
mtype = as_dc_charpointer(mime_type)
|
||||||
return msg
|
msg = Message.new(self._dc_context, "file")
|
||||||
|
msg.set_file(path, mtype)
|
||||||
|
msg_id = lib.dc_send_msg(self._dc_context, self.id, msg._dc_msg)
|
||||||
|
if msg_id == 0:
|
||||||
|
raise ValueError("message could not be send, does chat exist?")
|
||||||
|
return Message.from_db(self._dc_context, msg_id)
|
||||||
|
|
||||||
def send_image(self, path):
|
def send_image(self, path):
|
||||||
""" send an image message and return the resulting Message instance.
|
""" send an image message and return the resulting Message instance.
|
||||||
@@ -176,25 +152,14 @@ class Chat(object):
|
|||||||
:raises ValueError: if message can not be send/chat does not exist.
|
:raises ValueError: if message can not be send/chat does not exist.
|
||||||
:returns: the resulting :class:`deltachat.message.Message` instance
|
:returns: the resulting :class:`deltachat.message.Message` instance
|
||||||
"""
|
"""
|
||||||
mime_type = mimetypes.guess_type(path)[0]
|
if not os.path.exists(path):
|
||||||
msg = self.prepare_message_file(path=path, mime_type=mime_type, view_type="image")
|
raise ValueError("path does not exist: {!r}".format(path))
|
||||||
self.send_prepared(msg)
|
msg = Message.new(self._dc_context, "image")
|
||||||
return msg
|
msg.set_file(path)
|
||||||
|
msg_id = lib.dc_send_msg(self._dc_context, self.id, msg._dc_msg)
|
||||||
|
return Message.from_db(self._dc_context, msg_id)
|
||||||
|
|
||||||
def prepare_message(self, msg):
|
def prepare_file(self, path, mime_type=None, view_type="file"):
|
||||||
""" create a new prepared message.
|
|
||||||
|
|
||||||
:param msg: the message to be prepared.
|
|
||||||
:returns: :class:`deltachat.message.Message` instance.
|
|
||||||
"""
|
|
||||||
msg_id = lib.dc_prepare_msg(self._dc_context, self.id, msg._dc_msg)
|
|
||||||
if msg_id == 0:
|
|
||||||
raise ValueError("message could not be prepared")
|
|
||||||
# invalidate passed in message which is not safe to use anymore
|
|
||||||
msg._dc_msg = msg.id = None
|
|
||||||
return Message.from_db(self.account, msg_id)
|
|
||||||
|
|
||||||
def prepare_message_file(self, path, mime_type=None, view_type="file"):
|
|
||||||
""" prepare a message for sending and return the resulting Message instance.
|
""" prepare a message for sending and return the resulting Message instance.
|
||||||
|
|
||||||
To actually send the message, call :meth:`send_prepared`.
|
To actually send the message, call :meth:`send_prepared`.
|
||||||
@@ -202,13 +167,18 @@ class Chat(object):
|
|||||||
|
|
||||||
:param path: path to the file.
|
:param path: path to the file.
|
||||||
:param mime_type: the mime-type of this file, defaults to auto-detection.
|
:param mime_type: the mime-type of this file, defaults to auto-detection.
|
||||||
:param view_type: "text", "image", "gif", "audio", "video", "file"
|
:param view_type: passed to :meth:`MessageType.new`.
|
||||||
:raises ValueError: if message can not be prepared/chat does not exist.
|
:raises ValueError: if message can not be prepared/chat does not exist.
|
||||||
:returns: the resulting :class:`Message` instance
|
:returns: the resulting :class:`Message` instance
|
||||||
"""
|
"""
|
||||||
msg = Message.new_empty(self.account, view_type)
|
path = as_dc_charpointer(path)
|
||||||
msg.set_file(path, mime_type)
|
mtype = as_dc_charpointer(mime_type)
|
||||||
return self.prepare_message(msg)
|
msg = Message.new(self._dc_context, view_type)
|
||||||
|
msg.set_file(path, mtype)
|
||||||
|
msg_id = lib.dc_prepare_msg(self._dc_context, self.id, msg._dc_msg)
|
||||||
|
if msg_id == 0:
|
||||||
|
raise ValueError("message could not be prepared, does chat exist?")
|
||||||
|
return Message.from_db(self._dc_context, msg_id)
|
||||||
|
|
||||||
def send_prepared(self, message):
|
def send_prepared(self, message):
|
||||||
""" send a previously prepared message.
|
""" send a previously prepared message.
|
||||||
@@ -216,42 +186,12 @@ class Chat(object):
|
|||||||
:param message: a :class:`Message` instance previously returned by
|
:param message: a :class:`Message` instance previously returned by
|
||||||
:meth:`prepare_file`.
|
:meth:`prepare_file`.
|
||||||
:raises ValueError: if message can not be sent.
|
:raises ValueError: if message can not be sent.
|
||||||
:returns: a :class:`deltachat.message.Message` instance as sent out.
|
:returns: a :class:`deltachat.message.Message` instance with updated state
|
||||||
"""
|
"""
|
||||||
assert message.id != 0 and message.is_out_preparing()
|
msg_id = lib.dc_send_msg(self._dc_context, 0, message._dc_msg)
|
||||||
# get a fresh copy of dc_msg, the core needs it
|
if msg_id == 0:
|
||||||
msg = Message.from_db(self.account, message.id)
|
|
||||||
|
|
||||||
# pass 0 as chat-id because core-docs say it's ok when out-preparing
|
|
||||||
sent_id = lib.dc_send_msg(self._dc_context, 0, msg._dc_msg)
|
|
||||||
if sent_id == 0:
|
|
||||||
raise ValueError("message could not be sent")
|
raise ValueError("message could not be sent")
|
||||||
assert sent_id == msg.id
|
return Message.from_db(self._dc_context, msg_id)
|
||||||
# modify message in place to avoid bad state for the caller
|
|
||||||
msg._dc_msg = Message.from_db(self.account, sent_id)._dc_msg
|
|
||||||
|
|
||||||
def set_draft(self, message):
|
|
||||||
""" set message as draft.
|
|
||||||
|
|
||||||
:param message: a :class:`Message` instance
|
|
||||||
:returns: None
|
|
||||||
"""
|
|
||||||
if message is None:
|
|
||||||
lib.dc_set_draft(self._dc_context, self.id, ffi.NULL)
|
|
||||||
else:
|
|
||||||
lib.dc_set_draft(self._dc_context, self.id, message._dc_msg)
|
|
||||||
|
|
||||||
def get_draft(self):
|
|
||||||
""" get draft message for this chat.
|
|
||||||
|
|
||||||
:param message: a :class:`Message` instance
|
|
||||||
:returns: Message object or None (if no draft available)
|
|
||||||
"""
|
|
||||||
x = lib.dc_get_draft(self._dc_context, self.id)
|
|
||||||
if x == ffi.NULL:
|
|
||||||
return None
|
|
||||||
dc_msg = ffi.gc(x, lib.dc_msg_unref)
|
|
||||||
return Message(self.account, dc_msg)
|
|
||||||
|
|
||||||
def get_messages(self):
|
def get_messages(self):
|
||||||
""" return list of messages in this chat.
|
""" return list of messages in this chat.
|
||||||
@@ -262,7 +202,7 @@ class Chat(object):
|
|||||||
lib.dc_get_chat_msgs(self._dc_context, self.id, 0, 0),
|
lib.dc_get_chat_msgs(self._dc_context, self.id, 0, 0),
|
||||||
lib.dc_array_unref
|
lib.dc_array_unref
|
||||||
)
|
)
|
||||||
return list(iter_array(dc_array, lambda x: Message.from_db(self.account, x)))
|
return list(iter_array(dc_array, lambda x: Message.from_db(self._dc_context, x)))
|
||||||
|
|
||||||
def count_fresh_messages(self):
|
def count_fresh_messages(self):
|
||||||
""" return number of fresh messages in this chat.
|
""" return number of fresh messages in this chat.
|
||||||
@@ -305,6 +245,7 @@ class Chat(object):
|
|||||||
def get_contacts(self):
|
def get_contacts(self):
|
||||||
""" get all contacts for this chat.
|
""" get all contacts for this chat.
|
||||||
:params: contact object.
|
:params: contact object.
|
||||||
|
:raises ValueError: if contact could not be added
|
||||||
:returns: list of :class:`deltachat.chatting.Contact` objects for this chat
|
:returns: list of :class:`deltachat.chatting.Contact` objects for this chat
|
||||||
|
|
||||||
"""
|
"""
|
||||||
@@ -315,46 +256,3 @@ class Chat(object):
|
|||||||
return list(iter_array(
|
return list(iter_array(
|
||||||
dc_array, lambda id: Contact(self._dc_context, id))
|
dc_array, lambda id: Contact(self._dc_context, id))
|
||||||
)
|
)
|
||||||
|
|
||||||
def set_profile_image(self, img_path):
|
|
||||||
"""Set group profile image.
|
|
||||||
|
|
||||||
If the group is already promoted (any message was sent to the group),
|
|
||||||
all group members are informed by a special status message that is sent
|
|
||||||
automatically by this function.
|
|
||||||
:params img_path: path to image object
|
|
||||||
:raises ValueError: if profile image could not be set
|
|
||||||
:returns: None
|
|
||||||
"""
|
|
||||||
assert os.path.exists(img_path), img_path
|
|
||||||
p = as_dc_charpointer(img_path)
|
|
||||||
res = lib.dc_set_chat_profile_image(self._dc_context, self.id, p)
|
|
||||||
if res != 1:
|
|
||||||
raise ValueError("Setting Profile Image {!r} failed".format(p))
|
|
||||||
|
|
||||||
def remove_profile_image(self):
|
|
||||||
"""remove group profile image.
|
|
||||||
|
|
||||||
If the group is already promoted (any message was sent to the group),
|
|
||||||
all group members are informed by a special status message that is sent
|
|
||||||
automatically by this function.
|
|
||||||
:raises ValueError: if profile image could not be reset
|
|
||||||
:returns: None
|
|
||||||
"""
|
|
||||||
res = lib.dc_set_chat_profile_image(self._dc_context, self.id, ffi.NULL)
|
|
||||||
if res != 1:
|
|
||||||
raise ValueError("Removing Profile Image failed")
|
|
||||||
|
|
||||||
def get_profile_image(self):
|
|
||||||
"""Get group profile image.
|
|
||||||
|
|
||||||
For groups, this is the image set by any group member using
|
|
||||||
set_chat_profile_image(). For normal chats, this is the image
|
|
||||||
set by each remote user on their own using dc_set_config(context,
|
|
||||||
"selfavatar", image).
|
|
||||||
:returns: path to profile image, None if no profile image exists.
|
|
||||||
"""
|
|
||||||
dc_res = lib.dc_chat_get_profile_image(self._dc_chat)
|
|
||||||
if dc_res == ffi.NULL:
|
|
||||||
return None
|
|
||||||
return from_dc_charpointer(dc_res)
|
|
||||||
|
|||||||
@@ -8,23 +8,11 @@ from os.path import join as joinpath
|
|||||||
# this works well when you in a git-checkout
|
# this works well when you in a git-checkout
|
||||||
# run "python deltachat/const.py" to regenerate events
|
# run "python deltachat/const.py" to regenerate events
|
||||||
# begin const generated
|
# begin const generated
|
||||||
DC_PROVIDER_STATUS_OK = 1
|
|
||||||
DC_PROVIDER_STATUS_PREPARATION = 2
|
|
||||||
DC_PROVIDER_STATUS_BROKEN = 3
|
|
||||||
DC_GCL_ARCHIVED_ONLY = 0x01
|
DC_GCL_ARCHIVED_ONLY = 0x01
|
||||||
DC_GCL_NO_SPECIALS = 0x02
|
DC_GCL_NO_SPECIALS = 0x02
|
||||||
DC_GCL_ADD_ALLDONE_HINT = 0x04
|
DC_GCL_ADD_ALLDONE_HINT = 0x04
|
||||||
DC_GCL_VERIFIED_ONLY = 0x01
|
DC_GCL_VERIFIED_ONLY = 0x01
|
||||||
DC_GCL_ADD_SELF = 0x02
|
DC_GCL_ADD_SELF = 0x02
|
||||||
DC_QR_ASK_VERIFYCONTACT = 200
|
|
||||||
DC_QR_ASK_VERIFYGROUP = 202
|
|
||||||
DC_QR_FPR_OK = 210
|
|
||||||
DC_QR_FPR_MISMATCH = 220
|
|
||||||
DC_QR_FPR_WITHOUT_ADDR = 230
|
|
||||||
DC_QR_ADDR = 320
|
|
||||||
DC_QR_TEXT = 330
|
|
||||||
DC_QR_URL = 332
|
|
||||||
DC_QR_ERROR = 400
|
|
||||||
DC_CHAT_ID_DEADDROP = 1
|
DC_CHAT_ID_DEADDROP = 1
|
||||||
DC_CHAT_ID_TRASH = 3
|
DC_CHAT_ID_TRASH = 3
|
||||||
DC_CHAT_ID_MSGS_IN_CREATION = 4
|
DC_CHAT_ID_MSGS_IN_CREATION = 4
|
||||||
@@ -81,14 +69,15 @@ DC_EVENT_IMEX_FILE_WRITTEN = 2052
|
|||||||
DC_EVENT_SECUREJOIN_INVITER_PROGRESS = 2060
|
DC_EVENT_SECUREJOIN_INVITER_PROGRESS = 2060
|
||||||
DC_EVENT_SECUREJOIN_JOINER_PROGRESS = 2061
|
DC_EVENT_SECUREJOIN_JOINER_PROGRESS = 2061
|
||||||
DC_EVENT_GET_STRING = 2091
|
DC_EVENT_GET_STRING = 2091
|
||||||
|
DC_EVENT_HTTP_GET = 2100
|
||||||
|
DC_EVENT_HTTP_POST = 2110
|
||||||
DC_EVENT_FILE_COPIED = 2055
|
DC_EVENT_FILE_COPIED = 2055
|
||||||
DC_EVENT_IS_OFFLINE = 2081
|
DC_EVENT_IS_OFFLINE = 2081
|
||||||
# end const generated
|
# end const generated
|
||||||
|
|
||||||
|
|
||||||
def read_event_defines(f):
|
def read_event_defines(f):
|
||||||
rex = re.compile(r'#define\s+((?:DC_EVENT_|DC_QR|DC_MSG|DC_STATE_|'
|
rex = re.compile(r'#define\s+((?:DC_EVENT_|DC_MSG|DC_STATE_|DC_CONTACT_ID_|DC_GCL|DC_CHAT)\S+)\s+([x\d]+).*')
|
||||||
r'DC_CONTACT_ID_|DC_GCL|DC_CHAT|DC_PROVIDER)\S+)\s+([x\d]+).*')
|
|
||||||
for line in f:
|
for line in f:
|
||||||
m = rex.match(line)
|
m = rex.match(line)
|
||||||
if m:
|
if m:
|
||||||
@@ -101,7 +90,7 @@ if __name__ == "__main__":
|
|||||||
if len(sys.argv) >= 2:
|
if len(sys.argv) >= 2:
|
||||||
deltah = sys.argv[1]
|
deltah = sys.argv[1]
|
||||||
else:
|
else:
|
||||||
deltah = joinpath(dirname(dirname(dirname(here_dir))), "deltachat-ffi", "deltachat.h")
|
deltah = joinpath(dirname(dirname(dirname(here_dir))), "src", "deltachat.h")
|
||||||
assert os.path.exists(deltah)
|
assert os.path.exists(deltah)
|
||||||
|
|
||||||
lines = []
|
lines = []
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
from .capi import lib
|
from .capi import lib
|
||||||
from .capi import ffi
|
from .capi import ffi
|
||||||
from datetime import datetime
|
|
||||||
|
|
||||||
|
|
||||||
def as_dc_charpointer(obj):
|
def as_dc_charpointer(obj):
|
||||||
@@ -17,30 +16,4 @@ def iter_array(dc_array_t, constructor):
|
|||||||
|
|
||||||
|
|
||||||
def from_dc_charpointer(obj):
|
def from_dc_charpointer(obj):
|
||||||
return ffi.string(ffi.gc(obj, lib.dc_str_unref)).decode("utf8")
|
return ffi.string(obj).decode("utf8")
|
||||||
|
|
||||||
|
|
||||||
class DCLot:
|
|
||||||
def __init__(self, dc_lot):
|
|
||||||
self._dc_lot = dc_lot
|
|
||||||
|
|
||||||
def id(self):
|
|
||||||
return lib.dc_lot_get_id(self._dc_lot)
|
|
||||||
|
|
||||||
def state(self):
|
|
||||||
return lib.dc_lot_get_state(self._dc_lot)
|
|
||||||
|
|
||||||
def text1(self):
|
|
||||||
return from_dc_charpointer(lib.dc_lot_get_text1(self._dc_lot))
|
|
||||||
|
|
||||||
def text1_meaning(self):
|
|
||||||
return lib.dc_lot_get_text1_meaning(self._dc_lot)
|
|
||||||
|
|
||||||
def text2(self):
|
|
||||||
return from_dc_charpointer(lib.dc_lot_get_text2(self._dc_lot))
|
|
||||||
|
|
||||||
def timestamp(self):
|
|
||||||
ts = lib.dc_lot_get_timestamp(self._dc_lot)
|
|
||||||
if ts == 0:
|
|
||||||
return None
|
|
||||||
return datetime.utcfromtimestamp(ts)
|
|
||||||
|
|||||||
@@ -1,55 +1,59 @@
|
|||||||
""" chatting related objects: Contact, Chat, Message. """
|
""" chatting related objects: Contact, Chat, Message. """
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import shutil
|
|
||||||
from . import props
|
from . import props
|
||||||
from .cutil import from_dc_charpointer, as_dc_charpointer
|
from .cutil import from_dc_charpointer, as_dc_charpointer
|
||||||
from .capi import lib, ffi
|
from .capi import lib, ffi
|
||||||
from . import const
|
from . import const
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
import attr
|
||||||
|
from attr import validators as v
|
||||||
|
|
||||||
|
|
||||||
|
@attr.s
|
||||||
class Message(object):
|
class Message(object):
|
||||||
""" Message object.
|
""" Message object.
|
||||||
|
|
||||||
You obtain instances of it through :class:`deltachat.account.Account` or
|
You obtain instances of it through :class:`deltachat.account.Account` or
|
||||||
:class:`deltachat.chatting.Chat`.
|
:class:`deltachat.chatting.Chat`.
|
||||||
"""
|
"""
|
||||||
def __init__(self, account, dc_msg):
|
_dc_context = attr.ib(validator=v.instance_of(ffi.CData))
|
||||||
self.account = account
|
try:
|
||||||
self._dc_context = account._dc_context
|
id = attr.ib(validator=v.instance_of((int, long)))
|
||||||
assert isinstance(self._dc_context, ffi.CData)
|
except NameError: # py35
|
||||||
assert isinstance(dc_msg, ffi.CData)
|
id = attr.ib(validator=v.instance_of(int))
|
||||||
assert dc_msg != ffi.NULL
|
|
||||||
self._dc_msg = dc_msg
|
|
||||||
self.id = lib.dc_msg_get_id(dc_msg)
|
|
||||||
assert self.id is not None and self.id >= 0, repr(self.id)
|
|
||||||
|
|
||||||
def __eq__(self, other):
|
@property
|
||||||
return self.account == other.account and self.id == other.id
|
def _dc_msg(self):
|
||||||
|
if self.id > 0:
|
||||||
def __repr__(self):
|
return ffi.gc(
|
||||||
return "<Message id={} dc_context={}>".format(self.id, self._dc_context)
|
lib.dc_get_msg(self._dc_context, self.id),
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def from_db(cls, account, id):
|
|
||||||
assert id > 0
|
|
||||||
return cls(account, ffi.gc(
|
|
||||||
lib.dc_get_msg(account._dc_context, id),
|
|
||||||
lib.dc_msg_unref
|
lib.dc_msg_unref
|
||||||
))
|
)
|
||||||
|
return self._dc_msg_volatile
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def new_empty(cls, account, view_type):
|
def from_db(cls, _dc_context, id):
|
||||||
""" create a non-persistent message.
|
assert id > 0
|
||||||
|
return cls(_dc_context, id)
|
||||||
|
|
||||||
:param: view_type is "text", "audio", "video", "file"
|
@classmethod
|
||||||
"""
|
def new(cls, dc_context, view_type):
|
||||||
view_type_code = get_viewtype_code_from_name(view_type)
|
""" create a non-persistent method. """
|
||||||
return Message(account, ffi.gc(
|
msg = cls(dc_context, 0)
|
||||||
lib.dc_msg_new(account._dc_context, view_type_code),
|
view_type_code = MessageType.get_typecode(view_type)
|
||||||
|
msg._dc_msg_volatile = ffi.gc(
|
||||||
|
lib.dc_msg_new(dc_context, view_type_code),
|
||||||
lib.dc_msg_unref
|
lib.dc_msg_unref
|
||||||
))
|
)
|
||||||
|
return msg
|
||||||
|
|
||||||
|
def get_state(self):
|
||||||
|
""" get the message in/out state.
|
||||||
|
|
||||||
|
:returns: :class:`deltachat.message.MessageState`
|
||||||
|
"""
|
||||||
|
return MessageState(self)
|
||||||
|
|
||||||
@props.with_doc
|
@props.with_doc
|
||||||
def text(self):
|
def text(self):
|
||||||
@@ -58,9 +62,7 @@ class Message(object):
|
|||||||
|
|
||||||
def set_text(self, text):
|
def set_text(self, text):
|
||||||
"""set text of this message. """
|
"""set text of this message. """
|
||||||
assert self.id > 0, "message not prepared"
|
return lib.dc_msg_set_text(self._dc_msg, as_dc_charpointer(text))
|
||||||
assert self.is_out_preparing()
|
|
||||||
lib.dc_msg_set_text(self._dc_msg, as_dc_charpointer(text))
|
|
||||||
|
|
||||||
@props.with_doc
|
@props.with_doc
|
||||||
def filename(self):
|
def filename(self):
|
||||||
@@ -68,23 +70,9 @@ class Message(object):
|
|||||||
return from_dc_charpointer(lib.dc_msg_get_file(self._dc_msg))
|
return from_dc_charpointer(lib.dc_msg_get_file(self._dc_msg))
|
||||||
|
|
||||||
def set_file(self, path, mime_type=None):
|
def set_file(self, path, mime_type=None):
|
||||||
"""set file for this message from path and mime_type. """
|
"""set file for this message. """
|
||||||
mtype = ffi.NULL if mime_type is None else as_dc_charpointer(mime_type)
|
mtype = ffi.NULL if mime_type is None else mime_type
|
||||||
if not os.path.exists(path):
|
assert os.path.exists(path)
|
||||||
raise ValueError("path does not exist: {!r}".format(path))
|
|
||||||
blobdir = self.account.get_blobdir()
|
|
||||||
if not path.startswith(blobdir):
|
|
||||||
for i in range(50):
|
|
||||||
ext = "" if i == 0 else "-" + str(i)
|
|
||||||
dest = os.path.join(blobdir, os.path.basename(path) + ext)
|
|
||||||
if os.path.exists(dest):
|
|
||||||
continue
|
|
||||||
shutil.copyfile(path, dest)
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
raise ValueError("could not create blobdir-path for {}".format(path))
|
|
||||||
path = dest
|
|
||||||
assert path.startswith(blobdir), path
|
|
||||||
lib.dc_msg_set_file(self._dc_msg, as_dc_charpointer(path), mtype)
|
lib.dc_msg_set_file(self._dc_msg, as_dc_charpointer(path), mtype)
|
||||||
|
|
||||||
@props.with_doc
|
@props.with_doc
|
||||||
@@ -97,26 +85,21 @@ class Message(object):
|
|||||||
"""mime type of the file (if it exists)"""
|
"""mime type of the file (if it exists)"""
|
||||||
return from_dc_charpointer(lib.dc_msg_get_filemime(self._dc_msg))
|
return from_dc_charpointer(lib.dc_msg_get_filemime(self._dc_msg))
|
||||||
|
|
||||||
|
@props.with_doc
|
||||||
|
def view_type(self):
|
||||||
|
"""the view type of this message.
|
||||||
|
|
||||||
|
:returns: a :class:`deltachat.message.MessageType` instance.
|
||||||
|
"""
|
||||||
|
return MessageType(lib.dc_msg_get_viewtype(self._dc_msg))
|
||||||
|
|
||||||
def is_setup_message(self):
|
def is_setup_message(self):
|
||||||
""" return True if this message is a setup message. """
|
""" return True if this message is a setup message. """
|
||||||
return lib.dc_msg_is_setupmessage(self._dc_msg)
|
return lib.dc_msg_is_setupmessage(self._dc_msg)
|
||||||
|
|
||||||
def get_message_info(self):
|
|
||||||
""" Return informational text for a single message.
|
|
||||||
|
|
||||||
The text is multiline and may contain eg. the raw text of the message.
|
|
||||||
"""
|
|
||||||
return from_dc_charpointer(lib.dc_get_msg_info(self._dc_context, self.id))
|
|
||||||
|
|
||||||
def continue_key_transfer(self, setup_code):
|
def continue_key_transfer(self, setup_code):
|
||||||
""" extract key and use it as primary key for this account. """
|
""" extract key and use it as primary key for this account. """
|
||||||
res = lib.dc_continue_key_transfer(
|
lib.dc_continue_key_transfer(self._dc_context, self.id, as_dc_charpointer(setup_code))
|
||||||
self._dc_context,
|
|
||||||
self.id,
|
|
||||||
as_dc_charpointer(setup_code)
|
|
||||||
)
|
|
||||||
if res == 0:
|
|
||||||
raise ValueError("could not decrypt")
|
|
||||||
|
|
||||||
@props.with_doc
|
@props.with_doc
|
||||||
def time_sent(self):
|
def time_sent(self):
|
||||||
@@ -148,7 +131,7 @@ class Message(object):
|
|||||||
import email.parser
|
import email.parser
|
||||||
mime_headers = lib.dc_get_mime_headers(self._dc_context, self.id)
|
mime_headers = lib.dc_get_mime_headers(self._dc_context, self.id)
|
||||||
if mime_headers:
|
if mime_headers:
|
||||||
s = ffi.string(ffi.gc(mime_headers, lib.dc_str_unref))
|
s = ffi.string(mime_headers)
|
||||||
if isinstance(s, bytes):
|
if isinstance(s, bytes):
|
||||||
s = s.decode("ascii")
|
s = s.decode("ascii")
|
||||||
return email.message_from_string(s)
|
return email.message_from_string(s)
|
||||||
@@ -161,7 +144,7 @@ class Message(object):
|
|||||||
"""
|
"""
|
||||||
from .chatting import Chat
|
from .chatting import Chat
|
||||||
chat_id = lib.dc_msg_get_chat_id(self._dc_msg)
|
chat_id = lib.dc_msg_get_chat_id(self._dc_msg)
|
||||||
return Chat(self.account, chat_id)
|
return Chat(self._dc_context, chat_id)
|
||||||
|
|
||||||
def get_sender_contact(self):
|
def get_sender_contact(self):
|
||||||
"""return the contact of who wrote the message.
|
"""return the contact of who wrote the message.
|
||||||
@@ -172,20 +155,66 @@ class Message(object):
|
|||||||
contact_id = lib.dc_msg_get_from_id(self._dc_msg)
|
contact_id = lib.dc_msg_get_from_id(self._dc_msg)
|
||||||
return Contact(self._dc_context, contact_id)
|
return Contact(self._dc_context, contact_id)
|
||||||
|
|
||||||
#
|
|
||||||
# Message State query methods
|
@attr.s
|
||||||
#
|
class MessageType(object):
|
||||||
|
""" DeltaChat message type, with is_* methods. """
|
||||||
|
_type = attr.ib(validator=v.instance_of(int))
|
||||||
|
_mapping = {
|
||||||
|
const.DC_MSG_TEXT: 'text',
|
||||||
|
const.DC_MSG_IMAGE: 'image',
|
||||||
|
const.DC_MSG_GIF: 'gif',
|
||||||
|
const.DC_MSG_AUDIO: 'audio',
|
||||||
|
const.DC_MSG_VIDEO: 'video',
|
||||||
|
const.DC_MSG_FILE: 'file'
|
||||||
|
}
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_typecode(cls, view_type):
|
||||||
|
for code, value in cls._mapping.items():
|
||||||
|
if value == view_type:
|
||||||
|
return code
|
||||||
|
raise ValueError("message typecode not found for {!r}".format(view_type))
|
||||||
|
|
||||||
|
@props.with_doc
|
||||||
|
def name(self):
|
||||||
|
""" human readable type name. """
|
||||||
|
return self._mapping.get(self._type, "")
|
||||||
|
|
||||||
|
def is_text(self):
|
||||||
|
""" return True if it's a text message. """
|
||||||
|
return self._type == const.DC_MSG_TEXT
|
||||||
|
|
||||||
|
def is_image(self):
|
||||||
|
""" return True if it's an image message. """
|
||||||
|
return self._type == const.DC_MSG_IMAGE
|
||||||
|
|
||||||
|
def is_gif(self):
|
||||||
|
""" return True if it's a gif message. """
|
||||||
|
return self._type == const.DC_MSG_GIF
|
||||||
|
|
||||||
|
def is_audio(self):
|
||||||
|
""" return True if it's an audio message. """
|
||||||
|
return self._type == const.DC_MSG_AUDIO
|
||||||
|
|
||||||
|
def is_video(self):
|
||||||
|
""" return True if it's a video message. """
|
||||||
|
return self._type == const.DC_MSG_VIDEO
|
||||||
|
|
||||||
|
def is_file(self):
|
||||||
|
""" return True if it's a file message. """
|
||||||
|
return self._type == const.DC_MSG_FILE
|
||||||
|
|
||||||
|
|
||||||
|
@attr.s
|
||||||
|
class MessageState(object):
|
||||||
|
""" Current Message In/Out state, updated on each call of is_* methods.
|
||||||
|
"""
|
||||||
|
message = attr.ib(validator=v.instance_of(Message))
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def _msgstate(self):
|
def _msgstate(self):
|
||||||
if self.id == 0:
|
return lib.dc_msg_get_state(self.message._dc_msg)
|
||||||
dc_msg = self.message._dc_msg
|
|
||||||
else:
|
|
||||||
# load message from db to get a fresh/current state
|
|
||||||
dc_msg = ffi.gc(
|
|
||||||
lib.dc_get_msg(self._dc_context, self.id),
|
|
||||||
lib.dc_msg_unref
|
|
||||||
)
|
|
||||||
return lib.dc_msg_get_state(dc_msg)
|
|
||||||
|
|
||||||
def is_in_fresh(self):
|
def is_in_fresh(self):
|
||||||
""" return True if Message is incoming fresh message (un-noticed).
|
""" return True if Message is incoming fresh message (un-noticed).
|
||||||
@@ -239,56 +268,3 @@ class Message(object):
|
|||||||
state, you'll receive the event DC_EVENT_MSG_READ.
|
state, you'll receive the event DC_EVENT_MSG_READ.
|
||||||
"""
|
"""
|
||||||
return self._msgstate == const.DC_STATE_OUT_MDN_RCVD
|
return self._msgstate == const.DC_STATE_OUT_MDN_RCVD
|
||||||
|
|
||||||
#
|
|
||||||
# Message type query methods
|
|
||||||
#
|
|
||||||
|
|
||||||
@property
|
|
||||||
def _view_type(self):
|
|
||||||
assert self.id > 0
|
|
||||||
return lib.dc_msg_get_viewtype(self._dc_msg)
|
|
||||||
|
|
||||||
def is_text(self):
|
|
||||||
""" return True if it's a text message. """
|
|
||||||
return self._view_type == const.DC_MSG_TEXT
|
|
||||||
|
|
||||||
def is_image(self):
|
|
||||||
""" return True if it's an image message. """
|
|
||||||
return self._view_type == const.DC_MSG_IMAGE
|
|
||||||
|
|
||||||
def is_gif(self):
|
|
||||||
""" return True if it's a gif message. """
|
|
||||||
return self._view_type == const.DC_MSG_GIF
|
|
||||||
|
|
||||||
def is_audio(self):
|
|
||||||
""" return True if it's an audio message. """
|
|
||||||
return self._view_type == const.DC_MSG_AUDIO
|
|
||||||
|
|
||||||
def is_video(self):
|
|
||||||
""" return True if it's a video message. """
|
|
||||||
return self._view_type == const.DC_MSG_VIDEO
|
|
||||||
|
|
||||||
def is_file(self):
|
|
||||||
""" return True if it's a file message. """
|
|
||||||
return self._view_type == const.DC_MSG_FILE
|
|
||||||
|
|
||||||
|
|
||||||
# some code for handling DC_MSG_* view types
|
|
||||||
|
|
||||||
_view_type_mapping = {
|
|
||||||
const.DC_MSG_TEXT: 'text',
|
|
||||||
const.DC_MSG_IMAGE: 'image',
|
|
||||||
const.DC_MSG_GIF: 'gif',
|
|
||||||
const.DC_MSG_AUDIO: 'audio',
|
|
||||||
const.DC_MSG_VIDEO: 'video',
|
|
||||||
const.DC_MSG_FILE: 'file'
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def get_viewtype_code_from_name(view_type_name):
|
|
||||||
for code, value in _view_type_mapping.items():
|
|
||||||
if value == view_type_name:
|
|
||||||
return code
|
|
||||||
raise ValueError("message typecode not found for {!r}, "
|
|
||||||
"available {!r}".format(view_type_name, list(_view_type_mapping.values())))
|
|
||||||
|
|||||||
@@ -1,67 +0,0 @@
|
|||||||
"""Provider info class."""
|
|
||||||
|
|
||||||
from .capi import ffi, lib
|
|
||||||
from .cutil import as_dc_charpointer, from_dc_charpointer
|
|
||||||
|
|
||||||
|
|
||||||
class ProviderNotFoundError(Exception):
|
|
||||||
"""The provider information was not found."""
|
|
||||||
|
|
||||||
|
|
||||||
class Provider(object):
|
|
||||||
"""Provider information.
|
|
||||||
|
|
||||||
:param domain: The domain to get the provider info for, this is
|
|
||||||
normally the part following the `@` of the domain.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, domain):
|
|
||||||
provider = ffi.gc(
|
|
||||||
lib.dc_provider_new_from_domain(as_dc_charpointer(domain)),
|
|
||||||
lib.dc_provider_unref,
|
|
||||||
)
|
|
||||||
if provider == ffi.NULL:
|
|
||||||
raise ProviderNotFoundError("Provider not found")
|
|
||||||
self._provider = provider
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def from_email(cls, email):
|
|
||||||
"""Create provider info from an email address.
|
|
||||||
|
|
||||||
:param email: Email address to get provider info for.
|
|
||||||
"""
|
|
||||||
return cls(email.split('@')[-1])
|
|
||||||
|
|
||||||
@property
|
|
||||||
def overview_page(self):
|
|
||||||
"""URL to the overview page of the provider on providers.delta.chat."""
|
|
||||||
return from_dc_charpointer(
|
|
||||||
lib.dc_provider_get_overview_page(self._provider))
|
|
||||||
|
|
||||||
@property
|
|
||||||
def name(self):
|
|
||||||
"""The name of the provider."""
|
|
||||||
return from_dc_charpointer(lib.dc_provider_get_name(self._provider))
|
|
||||||
|
|
||||||
@property
|
|
||||||
def markdown(self):
|
|
||||||
"""Content of the information page, formatted as markdown."""
|
|
||||||
return from_dc_charpointer(
|
|
||||||
lib.dc_provider_get_markdown(self._provider))
|
|
||||||
|
|
||||||
@property
|
|
||||||
def status_date(self):
|
|
||||||
"""The date the provider info was last updated, as a string."""
|
|
||||||
return from_dc_charpointer(
|
|
||||||
lib.dc_provider_get_status_date(self._provider))
|
|
||||||
|
|
||||||
@property
|
|
||||||
def status(self):
|
|
||||||
"""The status of the provider information.
|
|
||||||
|
|
||||||
This is one of the
|
|
||||||
:attr:`deltachat.const.DC_PROVIDER_STATUS_OK`,
|
|
||||||
:attr:`deltachat.const.DC_PROVIDER_STATUS_PREPARATION` or
|
|
||||||
:attr:`deltachat.const.DC_PROVIDER_STATUS_BROKEN` constants.
|
|
||||||
"""
|
|
||||||
return lib.dc_provider_get_status(self._provider)
|
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
from __future__ import print_function
|
from __future__ import print_function
|
||||||
import os
|
import os
|
||||||
import pytest
|
import pytest
|
||||||
import requests
|
|
||||||
import time
|
import time
|
||||||
from deltachat import Account
|
from deltachat import Account
|
||||||
|
from deltachat import props
|
||||||
from deltachat.capi import lib
|
from deltachat.capi import lib
|
||||||
import tempfile
|
import tempfile
|
||||||
|
|
||||||
@@ -16,17 +16,18 @@ def pytest_addoption(parser):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def pytest_configure(config):
|
@pytest.hookimpl(trylast=True)
|
||||||
cfg = config.getoption('--liveconfig')
|
def pytest_runtest_call(item):
|
||||||
if not cfg:
|
# perform early finalization because we otherwise get cloberred
|
||||||
cfg = os.getenv('DCC_PY_LIVECONFIG')
|
# output from concurrent threads printing between execution
|
||||||
if cfg:
|
# of the test function and the teardown phase of that test function
|
||||||
config.option.liveconfig = cfg
|
if "acfactory" in item.funcargs:
|
||||||
|
print("*"*30, "finalizing", "*"*30)
|
||||||
|
acfactory = item.funcargs["acfactory"]
|
||||||
|
acfactory.finalize()
|
||||||
|
|
||||||
|
|
||||||
def pytest_report_header(config, startdir):
|
def pytest_report_header(config, startdir):
|
||||||
summary = []
|
|
||||||
|
|
||||||
t = tempfile.mktemp()
|
t = tempfile.mktemp()
|
||||||
try:
|
try:
|
||||||
ac = Account(t, eventlogging=False)
|
ac = Account(t, eventlogging=False)
|
||||||
@@ -34,19 +35,10 @@ def pytest_report_header(config, startdir):
|
|||||||
ac.shutdown()
|
ac.shutdown()
|
||||||
finally:
|
finally:
|
||||||
os.remove(t)
|
os.remove(t)
|
||||||
summary.extend(['Deltachat core={} sqlite={}'.format(
|
return "Deltachat core={} sqlite={}".format(
|
||||||
info['deltachat_core_version'],
|
info['deltachat_core_version'],
|
||||||
info['sqlite_version'],
|
info['sqlite_version'],
|
||||||
)])
|
)
|
||||||
|
|
||||||
cfg = config.option.liveconfig
|
|
||||||
if cfg:
|
|
||||||
if "#" in cfg:
|
|
||||||
url, token = cfg.split("#", 1)
|
|
||||||
summary.append('Liveconfig provider: {}#<token ommitted>'.format(url))
|
|
||||||
else:
|
|
||||||
summary.append('Liveconfig file: {}'.format(cfg))
|
|
||||||
return summary
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope="session")
|
@pytest.fixture(scope="session")
|
||||||
@@ -62,56 +54,9 @@ def data():
|
|||||||
return Data()
|
return Data()
|
||||||
|
|
||||||
|
|
||||||
class SessionLiveConfigFromFile:
|
|
||||||
def __init__(self, fn):
|
|
||||||
self.fn = fn
|
|
||||||
self.configlist = []
|
|
||||||
for line in open(fn):
|
|
||||||
if line.strip() and not line.strip().startswith('#'):
|
|
||||||
d = {}
|
|
||||||
for part in line.split():
|
|
||||||
name, value = part.split("=")
|
|
||||||
d[name] = value
|
|
||||||
self.configlist.append(d)
|
|
||||||
|
|
||||||
def get(self, index):
|
|
||||||
return self.configlist[index]
|
|
||||||
|
|
||||||
def exists(self):
|
|
||||||
return bool(self.configlist)
|
|
||||||
|
|
||||||
|
|
||||||
class SessionLiveConfigFromURL:
|
|
||||||
def __init__(self, url, create_token):
|
|
||||||
self.configlist = []
|
|
||||||
for i in range(2):
|
|
||||||
res = requests.post(url, json={"token_create_user": int(create_token)})
|
|
||||||
if res.status_code != 200:
|
|
||||||
pytest.skip("creating newtmpuser failed {!r}".format(res))
|
|
||||||
d = res.json()
|
|
||||||
config = dict(addr=d["email"], mail_pw=d["password"])
|
|
||||||
self.configlist.append(config)
|
|
||||||
|
|
||||||
def get(self, index):
|
|
||||||
return self.configlist[index]
|
|
||||||
|
|
||||||
def exists(self):
|
|
||||||
return bool(self.configlist)
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope="session")
|
|
||||||
def session_liveconfig(request):
|
|
||||||
liveconfig_opt = request.config.option.liveconfig
|
|
||||||
if liveconfig_opt:
|
|
||||||
if liveconfig_opt.startswith("http"):
|
|
||||||
url, create_token = liveconfig_opt.split("#", 1)
|
|
||||||
return SessionLiveConfigFromURL(url, create_token)
|
|
||||||
else:
|
|
||||||
return SessionLiveConfigFromFile(liveconfig_opt)
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def acfactory(pytestconfig, tmpdir, request, session_liveconfig):
|
def acfactory(pytestconfig, tmpdir, request):
|
||||||
|
fn = pytestconfig.getoption("--liveconfig")
|
||||||
|
|
||||||
class AccountMaker:
|
class AccountMaker:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
@@ -125,17 +70,25 @@ def acfactory(pytestconfig, tmpdir, request, session_liveconfig):
|
|||||||
fin = self._finalizers.pop()
|
fin = self._finalizers.pop()
|
||||||
fin()
|
fin()
|
||||||
|
|
||||||
def make_account(self, path, logid):
|
@props.cached
|
||||||
ac = Account(path, logid=logid)
|
def configlist(self):
|
||||||
self._finalizers.append(ac.shutdown)
|
configlist = []
|
||||||
return ac
|
for line in open(fn):
|
||||||
|
if line.strip():
|
||||||
|
d = {}
|
||||||
|
for part in line.split():
|
||||||
|
name, value = part.split("=")
|
||||||
|
d[name] = value
|
||||||
|
configlist.append(d)
|
||||||
|
return configlist
|
||||||
|
|
||||||
def get_unconfigured_account(self):
|
def get_unconfigured_account(self):
|
||||||
self.offline_count += 1
|
self.offline_count += 1
|
||||||
tmpdb = tmpdir.join("offlinedb%d" % self.offline_count)
|
tmpdb = tmpdir.join("offlinedb%d" % self.offline_count)
|
||||||
ac = self.make_account(tmpdb.strpath, logid="ac{}".format(self.offline_count))
|
ac = Account(tmpdb.strpath, logid="ac{}".format(self.offline_count))
|
||||||
ac._evlogger.init_time = self.init_time
|
ac._evlogger.init_time = self.init_time
|
||||||
ac._evlogger.set_timeout(2)
|
ac._evlogger.set_timeout(2)
|
||||||
|
self._finalizers.append(ac.shutdown)
|
||||||
return ac
|
return ac
|
||||||
|
|
||||||
def get_configured_offline_account(self):
|
def get_configured_offline_account(self):
|
||||||
@@ -151,42 +104,31 @@ def acfactory(pytestconfig, tmpdir, request, session_liveconfig):
|
|||||||
return ac
|
return ac
|
||||||
|
|
||||||
def get_online_configuring_account(self):
|
def get_online_configuring_account(self):
|
||||||
if not session_liveconfig:
|
if not fn:
|
||||||
pytest.skip("specify DCC_PY_LIVECONFIG or --liveconfig")
|
pytest.skip("specify a --liveconfig file to run tests with real accounts")
|
||||||
configdict = session_liveconfig.get(self.live_count)
|
|
||||||
self.live_count += 1
|
self.live_count += 1
|
||||||
if "e2ee_enabled" not in configdict:
|
configdict = self.configlist.pop(0)
|
||||||
configdict["e2ee_enabled"] = "1"
|
|
||||||
tmpdb = tmpdir.join("livedb%d" % self.live_count)
|
tmpdb = tmpdir.join("livedb%d" % self.live_count)
|
||||||
ac = self.make_account(tmpdb.strpath, logid="ac{}".format(self.live_count))
|
ac = Account(tmpdb.strpath, logid="ac{}".format(self.live_count))
|
||||||
ac._evlogger.init_time = self.init_time
|
ac._evlogger.init_time = self.init_time
|
||||||
ac._evlogger.set_timeout(30)
|
ac._evlogger.set_timeout(30)
|
||||||
ac.configure(**configdict)
|
ac.configure(**configdict)
|
||||||
ac.start_threads()
|
ac.start_threads()
|
||||||
|
self._finalizers.append(ac.shutdown)
|
||||||
return ac
|
return ac
|
||||||
|
|
||||||
def get_two_online_accounts(self):
|
|
||||||
ac1 = self.get_online_configuring_account()
|
|
||||||
ac2 = self.get_online_configuring_account()
|
|
||||||
wait_successful_IMAP_SMTP_connection(ac1)
|
|
||||||
wait_configuration_progress(ac1, 1000)
|
|
||||||
wait_successful_IMAP_SMTP_connection(ac2)
|
|
||||||
wait_configuration_progress(ac2, 1000)
|
|
||||||
return ac1, ac2
|
|
||||||
|
|
||||||
def clone_online_account(self, account):
|
def clone_online_account(self, account):
|
||||||
self.live_count += 1
|
self.live_count += 1
|
||||||
tmpdb = tmpdir.join("livedb%d" % self.live_count)
|
tmpdb = tmpdir.join("livedb%d" % self.live_count)
|
||||||
ac = self.make_account(tmpdb.strpath, logid="ac{}".format(self.live_count))
|
ac = Account(tmpdb.strpath, logid="ac{}".format(self.live_count))
|
||||||
ac._evlogger.init_time = self.init_time
|
ac._evlogger.init_time = self.init_time
|
||||||
ac._evlogger.set_timeout(30)
|
ac._evlogger.set_timeout(30)
|
||||||
ac.configure(addr=account.get_config("addr"), mail_pw=account.get_config("mail_pw"))
|
ac.configure(addr=account.get_config("addr"), mail_pw=account.get_config("mail_pw"))
|
||||||
ac.start_threads()
|
ac.start_threads()
|
||||||
|
self._finalizers.append(ac.shutdown)
|
||||||
return ac
|
return ac
|
||||||
|
|
||||||
am = AccountMaker()
|
return AccountMaker()
|
||||||
request.addfinalizer(am.finalize)
|
|
||||||
return am
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
@@ -215,15 +157,6 @@ def wait_configuration_progress(account, target):
|
|||||||
break
|
break
|
||||||
|
|
||||||
|
|
||||||
def wait_securejoin_inviter_progress(account, target):
|
|
||||||
while 1:
|
|
||||||
evt_name, data1, data2 = \
|
|
||||||
account._evlogger.get_matching("DC_EVENT_SECUREJOIN_INVITER_PROGRESS")
|
|
||||||
if data2 >= target:
|
|
||||||
print("** SECUREJOINT-INVITER PROGRESS {}".format(target), account)
|
|
||||||
break
|
|
||||||
|
|
||||||
|
|
||||||
def wait_successful_IMAP_SMTP_connection(account):
|
def wait_successful_IMAP_SMTP_connection(account):
|
||||||
imap_ok = smtp_ok = False
|
imap_ok = smtp_ok = False
|
||||||
while not imap_ok or not smtp_ok:
|
while not imap_ok or not smtp_ok:
|
||||||
|
|||||||
@@ -1,19 +1,12 @@
|
|||||||
from __future__ import print_function
|
from __future__ import print_function
|
||||||
import pytest
|
import pytest
|
||||||
import os
|
import os
|
||||||
from deltachat import const, Account
|
from deltachat import const
|
||||||
from deltachat.message import Message
|
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
from conftest import wait_configuration_progress, wait_successful_IMAP_SMTP_connection, wait_securejoin_inviter_progress
|
from conftest import wait_configuration_progress, wait_successful_IMAP_SMTP_connection
|
||||||
|
|
||||||
|
|
||||||
class TestOfflineAccountBasic:
|
class TestOfflineAccount:
|
||||||
def test_wrong_db(self, tmpdir):
|
|
||||||
p = tmpdir.join("hello.db")
|
|
||||||
p.write("123")
|
|
||||||
with pytest.raises(ValueError):
|
|
||||||
Account(p.strpath)
|
|
||||||
|
|
||||||
def test_getinfo(self, acfactory):
|
def test_getinfo(self, acfactory):
|
||||||
ac1 = acfactory.get_unconfigured_account()
|
ac1 = acfactory.get_unconfigured_account()
|
||||||
d = ac1.get_info()
|
d = ac1.get_info()
|
||||||
@@ -58,22 +51,16 @@ class TestOfflineAccountBasic:
|
|||||||
with pytest.raises(KeyError):
|
with pytest.raises(KeyError):
|
||||||
ac1.get_config("123123")
|
ac1.get_config("123123")
|
||||||
|
|
||||||
|
|
||||||
class TestOfflineContact:
|
|
||||||
def test_contact_attr(self, acfactory):
|
def test_contact_attr(self, acfactory):
|
||||||
ac1 = acfactory.get_configured_offline_account()
|
ac1 = acfactory.get_configured_offline_account()
|
||||||
contact1 = ac1.create_contact(email="some1@hello.com", name="some1")
|
contact1 = ac1.create_contact(email="some1@hello.com", name="some1")
|
||||||
contact2 = ac1.create_contact(email="some1@hello.com", name="some1")
|
|
||||||
str(contact1)
|
|
||||||
repr(contact1)
|
|
||||||
assert contact1 == contact2
|
|
||||||
assert contact1.id
|
assert contact1.id
|
||||||
assert contact1.addr == "some1@hello.com"
|
assert contact1.addr == "some1@hello.com"
|
||||||
assert contact1.display_name == "some1"
|
assert contact1.display_name == "some1"
|
||||||
assert not contact1.is_blocked()
|
assert not contact1.is_blocked()
|
||||||
assert not contact1.is_verified()
|
assert not contact1.is_verified()
|
||||||
|
|
||||||
def test_get_contacts_and_delete(self, acfactory):
|
def test_get_contacts(self, acfactory):
|
||||||
ac1 = acfactory.get_configured_offline_account()
|
ac1 = acfactory.get_configured_offline_account()
|
||||||
contact1 = ac1.create_contact(email="some1@hello.com", name="some1")
|
contact1 = ac1.create_contact(email="some1@hello.com", name="some1")
|
||||||
contacts = ac1.get_contacts()
|
contacts = ac1.get_contacts()
|
||||||
@@ -86,48 +73,26 @@ class TestOfflineContact:
|
|||||||
contacts = ac1.get_contacts(with_self=True)
|
contacts = ac1.get_contacts(with_self=True)
|
||||||
assert len(contacts) == 2
|
assert len(contacts) == 2
|
||||||
|
|
||||||
assert ac1.delete_contact(contact1)
|
def test_chat(self, acfactory):
|
||||||
assert contact1 not in ac1.get_contacts()
|
|
||||||
|
|
||||||
def test_get_contacts_and_delete_fails(self, acfactory):
|
|
||||||
ac1 = acfactory.get_configured_offline_account()
|
ac1 = acfactory.get_configured_offline_account()
|
||||||
contact1 = ac1.create_contact(email="some1@example.com", name="some1")
|
|
||||||
chat = ac1.create_chat_by_contact(contact1)
|
|
||||||
chat.send_text("one messae")
|
|
||||||
assert not ac1.delete_contact(contact1)
|
|
||||||
|
|
||||||
|
|
||||||
class TestOfflineChat:
|
|
||||||
@pytest.fixture
|
|
||||||
def ac1(self, acfactory):
|
|
||||||
return acfactory.get_configured_offline_account()
|
|
||||||
|
|
||||||
@pytest.fixture
|
|
||||||
def chat1(self, ac1):
|
|
||||||
contact1 = ac1.create_contact("some1@hello.com", name="some1")
|
contact1 = ac1.create_contact("some1@hello.com", name="some1")
|
||||||
chat = ac1.create_chat_by_contact(contact1)
|
chat = ac1.create_chat_by_contact(contact1)
|
||||||
assert chat.id >= const.DC_CHAT_ID_LAST_SPECIAL, chat.id
|
assert chat.id >= const.DC_CHAT_ID_LAST_SPECIAL, chat.id
|
||||||
return chat
|
|
||||||
|
|
||||||
def test_display(self, chat1):
|
|
||||||
str(chat1)
|
|
||||||
repr(chat1)
|
|
||||||
|
|
||||||
def test_chat_idempotent(self, chat1, ac1):
|
|
||||||
contact1 = chat1.get_contacts()[0]
|
|
||||||
chat2 = ac1.create_chat_by_contact(contact1.id)
|
chat2 = ac1.create_chat_by_contact(contact1.id)
|
||||||
assert chat2.id == chat1.id
|
assert chat2.id == chat.id
|
||||||
assert chat2.get_name() == chat1.get_name()
|
assert chat2.get_name() == chat.get_name()
|
||||||
assert chat1 == chat2
|
assert chat == chat2
|
||||||
assert not (chat1 != chat2)
|
assert not (chat != chat2)
|
||||||
|
|
||||||
for ichat in ac1.get_chats():
|
for ichat in ac1.get_chats():
|
||||||
if ichat.id == chat1.id:
|
if ichat.id == chat.id:
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
pytest.fail("could not find chat")
|
pytest.fail("could not find chat")
|
||||||
|
|
||||||
def test_group_chat_creation(self, ac1):
|
def test_group_chat_creation(self, acfactory):
|
||||||
|
ac1 = acfactory.get_configured_offline_account()
|
||||||
contact1 = ac1.create_contact("some1@hello.com", name="some1")
|
contact1 = ac1.create_contact("some1@hello.com", name="some1")
|
||||||
contact2 = ac1.create_contact("some2@hello.com", name="some2")
|
contact2 = ac1.create_contact("some2@hello.com", name="some2")
|
||||||
chat = ac1.create_group_chat(name="title1")
|
chat = ac1.create_group_chat(name="title1")
|
||||||
@@ -140,89 +105,64 @@ class TestOfflineChat:
|
|||||||
chat.set_name("title2")
|
chat.set_name("title2")
|
||||||
assert chat.get_name() == "title2"
|
assert chat.get_name() == "title2"
|
||||||
|
|
||||||
@pytest.mark.parametrize("verified", [True, False])
|
def test_delete_and_send_fails(self, acfactory):
|
||||||
def test_group_chat_qr(self, acfactory, ac1, verified):
|
ac1 = acfactory.get_configured_offline_account()
|
||||||
ac2 = acfactory.get_configured_offline_account()
|
contact1 = ac1.create_contact("some1@hello.com", name="some1")
|
||||||
chat = ac1.create_group_chat(name="title1", verified=verified)
|
chat = ac1.create_chat_by_contact(contact1)
|
||||||
qr = chat.get_join_qr()
|
chat.delete()
|
||||||
assert ac2.check_qr(qr).is_ask_verifygroup
|
|
||||||
|
|
||||||
def test_get_set_profile_image_simple(self, ac1, data):
|
|
||||||
chat = ac1.create_group_chat(name="title1")
|
|
||||||
p = data.get_path("d.png")
|
|
||||||
chat.set_profile_image(p)
|
|
||||||
p2 = chat.get_profile_image()
|
|
||||||
assert open(p, "rb").read() == open(p2, "rb").read()
|
|
||||||
chat.remove_profile_image()
|
|
||||||
assert chat.get_profile_image() is None
|
|
||||||
|
|
||||||
def test_delete_and_send_fails(self, ac1, chat1):
|
|
||||||
chat1.delete()
|
|
||||||
ac1._evlogger.get_matching("DC_EVENT_MSGS_CHANGED")
|
ac1._evlogger.get_matching("DC_EVENT_MSGS_CHANGED")
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError):
|
||||||
chat1.send_text("msg1")
|
chat.send_text("msg1")
|
||||||
|
|
||||||
def test_prepare_message_and_send(self, ac1, chat1):
|
def test_create_message(self, acfactory):
|
||||||
msg = chat1.prepare_message(Message.new_empty(chat1.account, "text"))
|
ac1 = acfactory.get_configured_offline_account()
|
||||||
msg.set_text("hello world")
|
message = ac1.create_message("text")
|
||||||
assert msg.text == "hello world"
|
assert message.id == 0
|
||||||
assert msg.id > 0
|
assert message._dc_msg is message._dc_msg
|
||||||
chat1.send_prepared(msg)
|
message.set_text("hello")
|
||||||
assert "Sent" in msg.get_message_info()
|
assert message.text == "hello"
|
||||||
str(msg)
|
assert message.id == 0
|
||||||
repr(msg)
|
|
||||||
assert msg == ac1.get_message_by_id(msg.id)
|
|
||||||
|
|
||||||
def test_prepare_file(self, ac1, chat1):
|
def test_message(self, acfactory):
|
||||||
blobdir = ac1.get_blobdir()
|
ac1 = acfactory.get_configured_offline_account()
|
||||||
p = os.path.join(blobdir, "somedata.txt")
|
contact1 = ac1.create_contact("some1@hello.com", name="some1")
|
||||||
with open(p, "w") as f:
|
chat = ac1.create_chat_by_contact(contact1)
|
||||||
f.write("some data")
|
msg = chat.send_text("msg1")
|
||||||
message = chat1.prepare_message_file(p)
|
|
||||||
assert message.id > 0
|
|
||||||
message.set_text("hello world")
|
|
||||||
assert message.is_out_preparing()
|
|
||||||
assert message.text == "hello world"
|
|
||||||
chat1.send_prepared(message)
|
|
||||||
assert "Sent" in message.get_message_info()
|
|
||||||
|
|
||||||
def test_message_eq_contains(self, chat1):
|
|
||||||
msg = chat1.send_text("msg1")
|
|
||||||
assert msg in chat1.get_messages()
|
|
||||||
assert not (msg not in chat1.get_messages())
|
|
||||||
str(msg)
|
|
||||||
repr(msg)
|
|
||||||
|
|
||||||
def test_message_send_text(self, chat1):
|
|
||||||
msg = chat1.send_text("msg1")
|
|
||||||
assert msg
|
assert msg
|
||||||
assert msg.is_text()
|
assert msg.view_type.is_text()
|
||||||
assert not msg.is_audio()
|
assert msg.view_type.name == "text"
|
||||||
assert not msg.is_video()
|
assert not msg.view_type.is_audio()
|
||||||
assert not msg.is_gif()
|
assert not msg.view_type.is_video()
|
||||||
assert not msg.is_file()
|
assert not msg.view_type.is_gif()
|
||||||
assert not msg.is_image()
|
assert not msg.view_type.is_file()
|
||||||
|
assert not msg.view_type.is_image()
|
||||||
|
msg_state = msg.get_state()
|
||||||
|
assert not msg_state.is_in_fresh()
|
||||||
|
assert not msg_state.is_in_noticed()
|
||||||
|
assert not msg_state.is_in_seen()
|
||||||
|
assert msg_state.is_out_pending()
|
||||||
|
assert not msg_state.is_out_failed()
|
||||||
|
assert not msg_state.is_out_delivered()
|
||||||
|
assert not msg_state.is_out_mdn_received()
|
||||||
|
|
||||||
assert not msg.is_in_fresh()
|
def test_create_chat_by_mssage_id(self, acfactory):
|
||||||
assert not msg.is_in_noticed()
|
ac1 = acfactory.get_configured_offline_account()
|
||||||
assert not msg.is_in_seen()
|
contact1 = ac1.create_contact("some1@hello.com", name="some1")
|
||||||
assert msg.is_out_pending()
|
chat = ac1.create_chat_by_contact(contact1)
|
||||||
assert not msg.is_out_failed()
|
msg = chat.send_text("msg1")
|
||||||
assert not msg.is_out_delivered()
|
assert chat == ac1.create_chat_by_message(msg)
|
||||||
assert not msg.is_out_mdn_received()
|
assert chat == ac1.create_chat_by_message(msg.id)
|
||||||
|
|
||||||
def test_create_chat_by_message_id(self, ac1, chat1):
|
def test_message_image(self, acfactory, data, lp):
|
||||||
msg = chat1.send_text("msg1")
|
ac1 = acfactory.get_configured_offline_account()
|
||||||
assert chat1 == ac1.create_chat_by_message(msg)
|
contact1 = ac1.create_contact("some1@hello.com", name="some1")
|
||||||
assert chat1 == ac1.create_chat_by_message(msg.id)
|
chat = ac1.create_chat_by_contact(contact1)
|
||||||
|
|
||||||
def test_message_image(self, chat1, data, lp):
|
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError):
|
||||||
chat1.send_image(path="notexists")
|
chat.send_image(path="notexists")
|
||||||
fn = data.get_path("d.png")
|
fn = data.get_path("d.png")
|
||||||
lp.sec("sending image")
|
lp.sec("sending image")
|
||||||
msg = chat1.send_image(fn)
|
msg = chat.send_image(fn)
|
||||||
assert msg.is_image()
|
assert msg.view_type.name == "image"
|
||||||
assert msg
|
assert msg
|
||||||
assert msg.id > 0
|
assert msg.id > 0
|
||||||
assert os.path.exists(msg.filename)
|
assert os.path.exists(msg.filename)
|
||||||
@@ -233,19 +173,20 @@ class TestOfflineChat:
|
|||||||
("text/plain", "text/plain"),
|
("text/plain", "text/plain"),
|
||||||
("image/png", "image/png"),
|
("image/png", "image/png"),
|
||||||
])
|
])
|
||||||
def test_message_file(self, ac1, chat1, data, lp, typein, typeout):
|
def test_message_file(self, acfactory, data, lp, typein, typeout):
|
||||||
|
ac1 = acfactory.get_configured_offline_account()
|
||||||
|
contact1 = ac1.create_contact("some1@hello.com", name="some1")
|
||||||
|
chat = ac1.create_chat_by_contact(contact1)
|
||||||
lp.sec("sending file")
|
lp.sec("sending file")
|
||||||
fn = data.get_path("r.txt")
|
fn = data.get_path("r.txt")
|
||||||
msg = chat1.send_file(fn, typein)
|
msg = chat.send_file(fn, typein)
|
||||||
assert msg
|
assert msg
|
||||||
assert msg.id > 0
|
assert msg.id > 0
|
||||||
assert msg.is_file()
|
assert msg.view_type.name == "file"
|
||||||
|
assert msg.view_type.is_file()
|
||||||
assert os.path.exists(msg.filename)
|
assert os.path.exists(msg.filename)
|
||||||
assert msg.filename.endswith(msg.basename)
|
assert msg.filename.endswith(msg.basename)
|
||||||
assert msg.filemime == typeout
|
assert msg.filemime == typeout
|
||||||
msg2 = chat1.send_file(fn, typein)
|
|
||||||
assert msg2 != msg
|
|
||||||
assert msg2.filename != msg.filename
|
|
||||||
|
|
||||||
def test_create_chat_mismatch(self, acfactory):
|
def test_create_chat_mismatch(self, acfactory):
|
||||||
ac1 = acfactory.get_configured_offline_account()
|
ac1 = acfactory.get_configured_offline_account()
|
||||||
@@ -258,9 +199,12 @@ class TestOfflineChat:
|
|||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError):
|
||||||
ac2.create_chat_by_message(msg)
|
ac2.create_chat_by_message(msg)
|
||||||
|
|
||||||
def test_chat_message_distinctions(self, ac1, chat1):
|
def test_chat_message_distinctions(self, acfactory):
|
||||||
|
ac1 = acfactory.get_configured_offline_account()
|
||||||
|
contact1 = ac1.create_contact("some1@hello.com", name="some1")
|
||||||
|
chat = ac1.create_chat_by_contact(contact1)
|
||||||
past1s = datetime.utcnow() - timedelta(seconds=1)
|
past1s = datetime.utcnow() - timedelta(seconds=1)
|
||||||
msg = chat1.send_text("msg1")
|
msg = chat.send_text("msg1")
|
||||||
ts = msg.time_sent
|
ts = msg.time_sent
|
||||||
assert msg.time_received is None
|
assert msg.time_received is None
|
||||||
assert ts.strftime("Y")
|
assert ts.strftime("Y")
|
||||||
@@ -268,7 +212,8 @@ class TestOfflineChat:
|
|||||||
contact = msg.get_sender_contact()
|
contact = msg.get_sender_contact()
|
||||||
assert contact == ac1.get_self_contact()
|
assert contact == ac1.get_self_contact()
|
||||||
|
|
||||||
def test_basic_configure_ok_addr_setting_forbidden(self, ac1):
|
def test_basic_configure_ok_addr_setting_forbidden(self, acfactory):
|
||||||
|
ac1 = acfactory.get_configured_offline_account()
|
||||||
assert ac1.get_config("mail_pw")
|
assert ac1.get_config("mail_pw")
|
||||||
assert ac1.is_configured()
|
assert ac1.is_configured()
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError):
|
||||||
@@ -307,38 +252,17 @@ class TestOfflineChat:
|
|||||||
assert messages[0].text == "msg1"
|
assert messages[0].text == "msg1"
|
||||||
assert os.path.exists(messages[1].filename)
|
assert os.path.exists(messages[1].filename)
|
||||||
|
|
||||||
def test_ac_setup_message_fails(self, ac1):
|
def test_ac_setup_message_fails(self, acfactory):
|
||||||
|
ac1 = acfactory.get_configured_offline_account()
|
||||||
with pytest.raises(RuntimeError):
|
with pytest.raises(RuntimeError):
|
||||||
ac1.initiate_key_transfer()
|
ac1.initiate_key_transfer()
|
||||||
|
|
||||||
def test_set_get_draft(self, chat1):
|
|
||||||
msg = Message.new_empty(chat1.account, "text")
|
|
||||||
msg1 = chat1.prepare_message(msg)
|
|
||||||
msg1.set_text("hello")
|
|
||||||
chat1.set_draft(msg1)
|
|
||||||
msg1.set_text("obsolete")
|
|
||||||
msg2 = chat1.get_draft()
|
|
||||||
assert msg2.text == "hello"
|
|
||||||
chat1.set_draft(None)
|
|
||||||
assert chat1.get_draft() is None
|
|
||||||
|
|
||||||
def test_qr_setup_contact(self, acfactory, lp):
|
|
||||||
ac1 = acfactory.get_configured_offline_account()
|
|
||||||
ac2 = acfactory.get_configured_offline_account()
|
|
||||||
qr = ac1.get_setup_contact_qr()
|
|
||||||
assert qr.startswith("OPENPGP4FPR:")
|
|
||||||
res = ac2.check_qr(qr)
|
|
||||||
assert res.is_ask_verifycontact()
|
|
||||||
assert not res.is_ask_verifygroup()
|
|
||||||
assert res.contact_id == 10
|
|
||||||
|
|
||||||
|
|
||||||
class TestOnlineAccount:
|
class TestOnlineAccount:
|
||||||
def get_chat(self, ac1, ac2):
|
def test_one_account_init(self, acfactory):
|
||||||
c2 = ac1.create_contact(email=ac2.get_config("addr"))
|
ac1 = acfactory.get_online_configuring_account()
|
||||||
chat = ac1.create_chat_by_contact(c2)
|
wait_successful_IMAP_SMTP_connection(ac1)
|
||||||
assert chat.id >= const.DC_CHAT_ID_LAST_SPECIAL
|
wait_configuration_progress(ac1, 1000)
|
||||||
return chat
|
|
||||||
|
|
||||||
def test_one_account_send(self, acfactory):
|
def test_one_account_send(self, acfactory):
|
||||||
ac1 = acfactory.get_online_configuring_account()
|
ac1 = acfactory.get_online_configuring_account()
|
||||||
@@ -353,9 +277,16 @@ class TestOnlineAccount:
|
|||||||
ev = ac1._evlogger.get_matching("DC_EVENT_INCOMING_MSG|DC_EVENT_MSGS_CHANGED")
|
ev = ac1._evlogger.get_matching("DC_EVENT_INCOMING_MSG|DC_EVENT_MSGS_CHANGED")
|
||||||
assert ev[1] == msg_out.id
|
assert ev[1] == msg_out.id
|
||||||
|
|
||||||
def test_two_accounts_send_receive(self, acfactory):
|
def test_two_acocunts_send_receive(self, acfactory):
|
||||||
ac1, ac2 = acfactory.get_two_online_accounts()
|
ac1 = acfactory.get_online_configuring_account()
|
||||||
chat = self.get_chat(ac1, ac2)
|
ac2 = acfactory.get_online_configuring_account()
|
||||||
|
c2 = ac1.create_contact(email=ac2.get_config("addr"))
|
||||||
|
chat = ac1.create_chat_by_contact(c2)
|
||||||
|
assert chat.id >= const.DC_CHAT_ID_LAST_SPECIAL
|
||||||
|
wait_successful_IMAP_SMTP_connection(ac1)
|
||||||
|
wait_configuration_progress(ac1, 1000)
|
||||||
|
wait_successful_IMAP_SMTP_connection(ac2)
|
||||||
|
wait_configuration_progress(ac2, 1000)
|
||||||
|
|
||||||
msg_out = chat.send_text("message1")
|
msg_out = chat.send_text("message1")
|
||||||
|
|
||||||
@@ -366,8 +297,15 @@ class TestOnlineAccount:
|
|||||||
assert msg_in.text == "message1"
|
assert msg_in.text == "message1"
|
||||||
|
|
||||||
def test_forward_messages(self, acfactory):
|
def test_forward_messages(self, acfactory):
|
||||||
ac1, ac2 = acfactory.get_two_online_accounts()
|
ac1 = acfactory.get_online_configuring_account()
|
||||||
chat = self.get_chat(ac1, ac2)
|
ac2 = acfactory.get_online_configuring_account()
|
||||||
|
c2 = ac1.create_contact(email=ac2.get_config("addr"))
|
||||||
|
chat = ac1.create_chat_by_contact(c2)
|
||||||
|
assert chat.id >= const.DC_CHAT_ID_LAST_SPECIAL
|
||||||
|
wait_successful_IMAP_SMTP_connection(ac1)
|
||||||
|
wait_configuration_progress(ac1, 1000)
|
||||||
|
wait_successful_IMAP_SMTP_connection(ac2)
|
||||||
|
wait_configuration_progress(ac2, 1000)
|
||||||
|
|
||||||
msg_out = chat.send_text("message2")
|
msg_out = chat.send_text("message2")
|
||||||
|
|
||||||
@@ -391,10 +329,15 @@ class TestOnlineAccount:
|
|||||||
assert not chat3.get_messages()
|
assert not chat3.get_messages()
|
||||||
|
|
||||||
def test_send_and_receive_message(self, acfactory, lp):
|
def test_send_and_receive_message(self, acfactory, lp):
|
||||||
ac1, ac2 = acfactory.get_two_online_accounts()
|
lp.sec("starting accounts, waiting for configuration")
|
||||||
|
ac1 = acfactory.get_online_configuring_account()
|
||||||
|
ac2 = acfactory.get_online_configuring_account()
|
||||||
|
c2 = ac1.create_contact(email=ac2.get_config("addr"))
|
||||||
|
chat = ac1.create_chat_by_contact(c2)
|
||||||
|
assert chat.id >= const.DC_CHAT_ID_LAST_SPECIAL
|
||||||
|
|
||||||
lp.sec("ac1: create chat with ac2")
|
wait_configuration_progress(ac1, 1000)
|
||||||
chat = self.get_chat(ac1, ac2)
|
wait_configuration_progress(ac2, 1000)
|
||||||
|
|
||||||
lp.sec("sending text message from ac1 to ac2")
|
lp.sec("sending text message from ac1 to ac2")
|
||||||
msg_out = chat.send_text("message1")
|
msg_out = chat.send_text("message1")
|
||||||
@@ -402,7 +345,7 @@ class TestOnlineAccount:
|
|||||||
evt_name, data1, data2 = ev
|
evt_name, data1, data2 = ev
|
||||||
assert data1 == chat.id
|
assert data1 == chat.id
|
||||||
assert data2 == msg_out.id
|
assert data2 == msg_out.id
|
||||||
assert msg_out.is_out_delivered()
|
assert msg_out.get_state().is_out_delivered()
|
||||||
|
|
||||||
lp.sec("wait for ac2 to receive message")
|
lp.sec("wait for ac2 to receive message")
|
||||||
ev = ac2._evlogger.get_matching("DC_EVENT_MSGS_CHANGED")
|
ev = ac2._evlogger.get_matching("DC_EVENT_MSGS_CHANGED")
|
||||||
@@ -429,45 +372,20 @@ class TestOnlineAccount:
|
|||||||
lp.sec("mark message as seen on ac2, wait for changes on ac1")
|
lp.sec("mark message as seen on ac2, wait for changes on ac1")
|
||||||
ac2.mark_seen_messages([msg_in])
|
ac2.mark_seen_messages([msg_in])
|
||||||
lp.step("1")
|
lp.step("1")
|
||||||
ev = ac1._evlogger.get_matching("DC_EVENT_MSG_READ")
|
ac1._evlogger.get_matching("DC_EVENT_MSG_READ")
|
||||||
assert ev[1] >= const.DC_CHAT_ID_LAST_SPECIAL
|
|
||||||
assert ev[2] >= const.DC_MSG_ID_LAST_SPECIAL
|
|
||||||
lp.step("2")
|
lp.step("2")
|
||||||
assert msg_out.is_out_mdn_received()
|
# ac1._evlogger.get_info_matching("Message marked as seen")
|
||||||
|
assert msg_out.get_state().is_out_mdn_received()
|
||||||
def test_send_and_receive_will_encrypt_decrypt(self, acfactory, lp):
|
|
||||||
ac1, ac2 = acfactory.get_two_online_accounts()
|
|
||||||
|
|
||||||
lp.sec("ac1: create chat with ac2")
|
|
||||||
chat = self.get_chat(ac1, ac2)
|
|
||||||
|
|
||||||
lp.sec("sending text message from ac1 to ac2")
|
|
||||||
msg_out = chat.send_text("message1")
|
|
||||||
|
|
||||||
lp.sec("wait for ac2 to receive message")
|
|
||||||
ev = ac2._evlogger.get_matching("DC_EVENT_MSGS_CHANGED")
|
|
||||||
assert ev[2] == msg_out.id
|
|
||||||
msg_in = ac2.get_message_by_id(msg_out.id)
|
|
||||||
assert msg_in.text == "message1"
|
|
||||||
|
|
||||||
lp.sec("create new chat with contact and send back (encrypted) message")
|
|
||||||
chat2b = ac2.create_chat_by_message(msg_in)
|
|
||||||
chat2b.send_text("message-back")
|
|
||||||
|
|
||||||
lp.sec("wait for ac1 to receive message")
|
|
||||||
ev = ac1._evlogger.get_matching("DC_EVENT_INCOMING_MSG")
|
|
||||||
assert ev[1] == chat.id
|
|
||||||
assert ev[2] > msg_out.id
|
|
||||||
msg_back = ac1.get_message_by_id(ev[2])
|
|
||||||
assert msg_back.text == "message-back"
|
|
||||||
|
|
||||||
def test_saved_mime_on_received_message(self, acfactory, lp):
|
def test_saved_mime_on_received_message(self, acfactory, lp):
|
||||||
ac1, ac2 = acfactory.get_two_online_accounts()
|
lp.sec("starting accounts, waiting for configuration")
|
||||||
|
ac1 = acfactory.get_online_configuring_account()
|
||||||
lp.sec("configure ac2 to save mime headers, create ac1/ac2 chat")
|
ac2 = acfactory.get_online_configuring_account()
|
||||||
ac2.set_config("save_mime_headers", "1")
|
ac2.set_config("save_mime_headers", "1")
|
||||||
chat = self.get_chat(ac1, ac2)
|
c2 = ac1.create_contact(email=ac2.get_config("addr"))
|
||||||
|
chat = ac1.create_chat_by_contact(c2)
|
||||||
|
wait_configuration_progress(ac1, 1000)
|
||||||
|
wait_configuration_progress(ac2, 1000)
|
||||||
lp.sec("sending text message from ac1 to ac2")
|
lp.sec("sending text message from ac1 to ac2")
|
||||||
msg_out = chat.send_text("message1")
|
msg_out = chat.send_text("message1")
|
||||||
ac1._evlogger.get_matching("DC_EVENT_MSG_DELIVERED")
|
ac1._evlogger.get_matching("DC_EVENT_MSG_DELIVERED")
|
||||||
@@ -481,8 +399,14 @@ class TestOnlineAccount:
|
|||||||
assert mime.get_all("Received")
|
assert mime.get_all("Received")
|
||||||
|
|
||||||
def test_send_and_receive_image(self, acfactory, lp, data):
|
def test_send_and_receive_image(self, acfactory, lp, data):
|
||||||
ac1, ac2 = acfactory.get_two_online_accounts()
|
lp.sec("starting accounts, waiting for configuration")
|
||||||
chat = self.get_chat(ac1, ac2)
|
ac1 = acfactory.get_online_configuring_account()
|
||||||
|
ac2 = acfactory.get_online_configuring_account()
|
||||||
|
c2 = ac1.create_contact(email=ac2.get_config("addr"))
|
||||||
|
chat = ac1.create_chat_by_contact(c2)
|
||||||
|
|
||||||
|
wait_configuration_progress(ac1, 1000)
|
||||||
|
wait_configuration_progress(ac2, 1000)
|
||||||
|
|
||||||
lp.sec("sending image message from ac1 to ac2")
|
lp.sec("sending image message from ac1 to ac2")
|
||||||
path = data.get_path("d.png")
|
path = data.get_path("d.png")
|
||||||
@@ -491,24 +415,24 @@ class TestOnlineAccount:
|
|||||||
evt_name, data1, data2 = ev
|
evt_name, data1, data2 = ev
|
||||||
assert data1 == chat.id
|
assert data1 == chat.id
|
||||||
assert data2 == msg_out.id
|
assert data2 == msg_out.id
|
||||||
assert msg_out.is_out_delivered()
|
assert msg_out.get_state().is_out_delivered()
|
||||||
|
|
||||||
lp.sec("wait for ac2 to receive message")
|
lp.sec("wait for ac2 to receive message")
|
||||||
ev = ac2._evlogger.get_matching("DC_EVENT_MSGS_CHANGED")
|
ev = ac2._evlogger.get_matching("DC_EVENT_MSGS_CHANGED")
|
||||||
assert ev[2] == msg_out.id
|
assert ev[2] == msg_out.id
|
||||||
msg_in = ac2.get_message_by_id(msg_out.id)
|
msg_in = ac2.get_message_by_id(msg_out.id)
|
||||||
assert msg_in.is_image()
|
assert msg_in.view_type.is_image()
|
||||||
assert os.path.exists(msg_in.filename)
|
assert os.path.exists(msg_in.filename)
|
||||||
assert os.stat(msg_in.filename).st_size == os.stat(path).st_size
|
assert os.stat(msg_in.filename).st_size == os.stat(path).st_size
|
||||||
|
|
||||||
def test_import_export_online(self, acfactory, tmpdir):
|
def test_import_export_online(self, acfactory, tmpdir):
|
||||||
|
backupdir = tmpdir.mkdir("backup")
|
||||||
ac1 = acfactory.get_online_configuring_account()
|
ac1 = acfactory.get_online_configuring_account()
|
||||||
wait_configuration_progress(ac1, 1000)
|
wait_configuration_progress(ac1, 1000)
|
||||||
|
|
||||||
contact1 = ac1.create_contact("some1@hello.com", name="some1")
|
contact1 = ac1.create_contact("some1@hello.com", name="some1")
|
||||||
chat = ac1.create_chat_by_contact(contact1)
|
chat = ac1.create_chat_by_contact(contact1)
|
||||||
chat.send_text("msg1")
|
chat.send_text("msg1")
|
||||||
backupdir = tmpdir.mkdir("backup")
|
|
||||||
path = ac1.export_to_dir(backupdir.strpath)
|
path = ac1.export_to_dir(backupdir.strpath)
|
||||||
assert os.path.exists(path)
|
assert os.path.exists(path)
|
||||||
|
|
||||||
@@ -533,86 +457,11 @@ class TestOnlineAccount:
|
|||||||
wait_configuration_progress(ac1, 1000)
|
wait_configuration_progress(ac1, 1000)
|
||||||
assert ac1.get_info()["fingerprint"] != ac2.get_info()["fingerprint"]
|
assert ac1.get_info()["fingerprint"] != ac2.get_info()["fingerprint"]
|
||||||
setup_code = ac1.initiate_key_transfer()
|
setup_code = ac1.initiate_key_transfer()
|
||||||
ac2._evlogger.set_timeout(30)
|
ac2._evlogger.set_timeout(10)
|
||||||
ev = ac2._evlogger.get_matching("DC_EVENT_INCOMING_MSG|DC_EVENT_MSGS_CHANGED")
|
ev = ac2._evlogger.get_matching("DC_EVENT_INCOMING_MSG|DC_EVENT_MSGS_CHANGED")
|
||||||
msg = ac2.get_message_by_id(ev[2])
|
msg = ac2.get_message_by_id(ev[2])
|
||||||
assert msg.is_setup_message()
|
assert msg.is_setup_message()
|
||||||
# first try a bad setup code
|
|
||||||
with pytest.raises(ValueError):
|
|
||||||
msg.continue_key_transfer(str(reversed(setup_code)))
|
|
||||||
print("*************** Incoming ASM File at: ", msg.filename)
|
print("*************** Incoming ASM File at: ", msg.filename)
|
||||||
print("*************** Setup Code: ", setup_code)
|
print("*************** Setup Code: ", setup_code)
|
||||||
msg.continue_key_transfer(setup_code)
|
msg.continue_key_transfer(setup_code)
|
||||||
assert ac1.get_info()["fingerprint"] == ac2.get_info()["fingerprint"]
|
assert ac1.get_info()["fingerprint"] == ac2.get_info()["fingerprint"]
|
||||||
|
|
||||||
def test_qr_setup_contact(self, acfactory, lp):
|
|
||||||
ac1, ac2 = acfactory.get_two_online_accounts()
|
|
||||||
lp.sec("ac1: create QR code and let ac2 scan it, starting the securejoin")
|
|
||||||
qr = ac1.get_setup_contact_qr()
|
|
||||||
lp.sec("ac2: start QR-code based setup contact protocol")
|
|
||||||
ch = ac2.qr_setup_contact(qr)
|
|
||||||
assert ch.id >= 10
|
|
||||||
wait_securejoin_inviter_progress(ac1, 1000)
|
|
||||||
|
|
||||||
def test_qr_join_chat(self, acfactory, lp):
|
|
||||||
ac1, ac2 = acfactory.get_two_online_accounts()
|
|
||||||
lp.sec("ac1: create QR code and let ac2 scan it, starting the securejoin")
|
|
||||||
chat = ac1.create_group_chat("hello")
|
|
||||||
qr = chat.get_join_qr()
|
|
||||||
lp.sec("ac2: start QR-code based join-group protocol")
|
|
||||||
ch = ac2.qr_join_chat(qr)
|
|
||||||
assert ch.id >= 10
|
|
||||||
wait_securejoin_inviter_progress(ac1, 1000)
|
|
||||||
|
|
||||||
def test_set_get_profile_image(self, acfactory, data, lp):
|
|
||||||
ac1, ac2 = acfactory.get_two_online_accounts()
|
|
||||||
|
|
||||||
lp.sec("create unpromoted group chat")
|
|
||||||
chat = ac1.create_group_chat("hello")
|
|
||||||
p = data.get_path("d.png")
|
|
||||||
|
|
||||||
lp.sec("ac1: set profile image on unpromoted chat")
|
|
||||||
chat.set_profile_image(p)
|
|
||||||
ac1._evlogger.get_matching("DC_EVENT_CHAT_MODIFIED")
|
|
||||||
assert not chat.is_promoted()
|
|
||||||
|
|
||||||
lp.sec("ac1: send text to promote chat (XXX without contact added)")
|
|
||||||
# XXX first promote the chat before adding contact
|
|
||||||
# because DC does not send out profile images for unpromoted chats
|
|
||||||
# otherwise
|
|
||||||
chat.send_text("ac1: initial message to promote chat (workaround)")
|
|
||||||
assert chat.is_promoted()
|
|
||||||
|
|
||||||
lp.sec("ac2: add ac1 to a chat so the message does not land in DEADDROP")
|
|
||||||
c1 = ac2.create_contact(email=ac1.get_config("addr"))
|
|
||||||
ac2.create_chat_by_contact(c1)
|
|
||||||
ev = ac2._evlogger.get_matching("DC_EVENT_MSGS_CHANGED")
|
|
||||||
|
|
||||||
lp.sec("ac1: add ac2 to promoted group chat")
|
|
||||||
c2 = ac1.create_contact(email=ac2.get_config("addr"))
|
|
||||||
chat.add_contact(c2)
|
|
||||||
|
|
||||||
lp.sec("ac1: send a first message to ac2")
|
|
||||||
chat.send_text("hi")
|
|
||||||
assert chat.is_promoted()
|
|
||||||
|
|
||||||
lp.sec("ac2: wait for receiving message from ac1")
|
|
||||||
ev = ac2._evlogger.get_matching("DC_EVENT_INCOMING_MSG")
|
|
||||||
msg_in = ac2.get_message_by_id(ev[2])
|
|
||||||
assert not msg_in.chat.is_deaddrop()
|
|
||||||
|
|
||||||
lp.sec("ac2: create chat and read profile image")
|
|
||||||
chat2 = ac2.create_chat_by_message(msg_in)
|
|
||||||
p2 = chat2.get_profile_image()
|
|
||||||
assert p2 is not None
|
|
||||||
assert open(p2, "rb").read() == open(p, "rb").read()
|
|
||||||
|
|
||||||
ac2._evlogger.consume_events()
|
|
||||||
ac1._evlogger.consume_events()
|
|
||||||
lp.sec("ac2: delete profile image from chat")
|
|
||||||
chat2.remove_profile_image()
|
|
||||||
ev = ac1._evlogger.get_matching("DC_EVENT_INCOMING_MSG")
|
|
||||||
assert ev[1] == chat.id
|
|
||||||
chat1b = ac1.create_chat_by_message(ev[2])
|
|
||||||
assert chat1b.get_profile_image() is None
|
|
||||||
assert chat.get_profile_image() is None
|
|
||||||
|
|||||||
@@ -1,25 +1,30 @@
|
|||||||
from __future__ import print_function
|
from __future__ import print_function
|
||||||
|
import os
|
||||||
|
import shutil
|
||||||
from filecmp import cmp
|
from filecmp import cmp
|
||||||
from deltachat import const
|
from deltachat import const
|
||||||
from conftest import wait_configuration_progress, wait_msgs_changed
|
from conftest import wait_configuration_progress, wait_msgs_changed
|
||||||
|
|
||||||
|
|
||||||
class TestOnlineInCreation:
|
class TestInCreation:
|
||||||
def test_forward_increation(self, acfactory, data, lp):
|
def test_forward_increation(self, acfactory, data, lp):
|
||||||
ac1 = acfactory.get_online_configuring_account()
|
ac1 = acfactory.get_online_configuring_account()
|
||||||
ac2 = acfactory.get_online_configuring_account()
|
ac2 = acfactory.get_online_configuring_account()
|
||||||
wait_configuration_progress(ac1, 1000)
|
wait_configuration_progress(ac1, 1000)
|
||||||
wait_configuration_progress(ac2, 1000)
|
wait_configuration_progress(ac2, 1000)
|
||||||
|
|
||||||
|
blobdir = ac1.get_blobdir()
|
||||||
|
|
||||||
c2 = ac1.create_contact(email=ac2.get_config("addr"))
|
c2 = ac1.create_contact(email=ac2.get_config("addr"))
|
||||||
chat = ac1.create_chat_by_contact(c2)
|
chat = ac1.create_chat_by_contact(c2)
|
||||||
assert chat.id >= const.DC_CHAT_ID_LAST_SPECIAL
|
assert chat.id >= const.DC_CHAT_ID_LAST_SPECIAL
|
||||||
wait_msgs_changed(ac1, 0, 0) # why no chat id?
|
wait_msgs_changed(ac1, 0, 0) # why no chat id?
|
||||||
|
|
||||||
lp.sec("create a message with a file in creation")
|
lp.sec("create a message with a file in creation")
|
||||||
path = data.get_path("d.png")
|
path = os.path.join(blobdir, "d.png")
|
||||||
prepared_original = chat.prepare_message_file(path)
|
open(path, 'a').close()
|
||||||
assert prepared_original.is_out_preparing()
|
prepared_original = chat.prepare_file(path)
|
||||||
|
assert prepared_original.get_state().is_out_preparing()
|
||||||
wait_msgs_changed(ac1, chat.id, prepared_original.id)
|
wait_msgs_changed(ac1, chat.id, prepared_original.id)
|
||||||
|
|
||||||
lp.sec("forward the message while still in creation")
|
lp.sec("forward the message while still in creation")
|
||||||
@@ -27,41 +32,36 @@ class TestOnlineInCreation:
|
|||||||
chat2.add_contact(c2)
|
chat2.add_contact(c2)
|
||||||
wait_msgs_changed(ac1, 0, 0) # why not chat id?
|
wait_msgs_changed(ac1, 0, 0) # why not chat id?
|
||||||
ac1.forward_messages([prepared_original], chat2)
|
ac1.forward_messages([prepared_original], chat2)
|
||||||
# XXX there might be two EVENT_MSGS_CHANGED and only one of them
|
|
||||||
# is the one caused by forwarding
|
|
||||||
forwarded_id = wait_msgs_changed(ac1, chat2.id)
|
forwarded_id = wait_msgs_changed(ac1, chat2.id)
|
||||||
if forwarded_id == 0:
|
|
||||||
forwarded_id = wait_msgs_changed(ac1, chat2.id)
|
|
||||||
assert forwarded_id
|
|
||||||
forwarded_msg = ac1.get_message_by_id(forwarded_id)
|
forwarded_msg = ac1.get_message_by_id(forwarded_id)
|
||||||
assert forwarded_msg.is_out_preparing()
|
assert forwarded_msg.get_state().is_out_preparing()
|
||||||
|
|
||||||
lp.sec("finish creating the file and send it")
|
lp.sec("finish creating the file and send it")
|
||||||
assert prepared_original.is_out_preparing()
|
shutil.copy(data.get_path("d.png"), path)
|
||||||
chat.send_prepared(prepared_original)
|
sent_original = chat.send_prepared(prepared_original)
|
||||||
assert prepared_original.is_out_pending() or prepared_original.is_out_delivered()
|
assert sent_original.id == prepared_original.id
|
||||||
wait_msgs_changed(ac1, chat.id, prepared_original.id)
|
state = sent_original.get_state()
|
||||||
|
assert state.is_out_pending() or state.is_out_delivered()
|
||||||
|
wait_msgs_changed(ac1, chat.id, sent_original.id)
|
||||||
|
|
||||||
lp.sec("expect the forwarded message to be sent now too")
|
lp.sec("expect the forwarded message to be sent now too")
|
||||||
wait_msgs_changed(ac1, chat2.id, forwarded_id)
|
wait_msgs_changed(ac1, chat2.id, forwarded_id)
|
||||||
fwd_msg = ac1.get_message_by_id(forwarded_id)
|
state = ac1.get_message_by_id(forwarded_id).get_state()
|
||||||
assert fwd_msg.is_out_pending() or fwd_msg.is_out_delivered()
|
assert state.is_out_pending() or state.is_out_delivered()
|
||||||
|
|
||||||
lp.sec("wait for the messages to be delivered to SMTP")
|
lp.sec("wait for the messages to be delivered to SMTP")
|
||||||
ev = ac1._evlogger.get_matching("DC_EVENT_MSG_DELIVERED")
|
ev = ac1._evlogger.get_matching("DC_EVENT_MSG_DELIVERED")
|
||||||
assert ev[1] == chat.id
|
assert ev[1] == chat.id
|
||||||
assert ev[2] == prepared_original.id
|
assert ev[2] == sent_original.id
|
||||||
ev = ac1._evlogger.get_matching("DC_EVENT_MSG_DELIVERED")
|
ev = ac1._evlogger.get_matching("DC_EVENT_MSG_DELIVERED")
|
||||||
assert ev[1] == chat2.id
|
assert ev[1] == chat2.id
|
||||||
assert ev[2] == forwarded_id
|
assert ev[2] == forwarded_id
|
||||||
|
|
||||||
lp.sec("wait1 for original or forwarded messages to arrive")
|
lp.sec("wait for both messages to arrive")
|
||||||
ev1 = ac2._evlogger.get_matching("DC_EVENT_MSGS_CHANGED")
|
ev1 = ac2._evlogger.get_matching("DC_EVENT_MSGS_CHANGED")
|
||||||
assert ev1[1] >= const.DC_CHAT_ID_LAST_SPECIAL
|
assert ev1[1] >= const.DC_CHAT_ID_LAST_SPECIAL
|
||||||
received_original = ac2.get_message_by_id(ev1[2])
|
received_original = ac2.get_message_by_id(ev1[2])
|
||||||
assert cmp(received_original.filename, path, False)
|
assert cmp(received_original.filename, path, False)
|
||||||
|
|
||||||
lp.sec("wait2 for original or forwarded messages to arrive")
|
|
||||||
ev2 = ac2._evlogger.get_matching("DC_EVENT_MSGS_CHANGED")
|
ev2 = ac2._evlogger.get_matching("DC_EVENT_MSGS_CHANGED")
|
||||||
assert ev2[1] >= const.DC_CHAT_ID_LAST_SPECIAL
|
assert ev2[1] >= const.DC_CHAT_ID_LAST_SPECIAL
|
||||||
assert ev2[1] != ev1[1]
|
assert ev2[1] != ev1[1]
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
from __future__ import print_function
|
from __future__ import print_function
|
||||||
from deltachat import capi, cutil, const, set_context_callback, clear_context_callback
|
import pytest
|
||||||
|
from deltachat import capi, Account, const, set_context_callback, clear_context_callback
|
||||||
from deltachat.capi import ffi
|
from deltachat.capi import ffi
|
||||||
from deltachat.capi import lib
|
|
||||||
from deltachat.account import EventLogger
|
from deltachat.account import EventLogger
|
||||||
|
|
||||||
|
|
||||||
@@ -17,46 +17,23 @@ def test_callback_None2int():
|
|||||||
clear_context_callback(ctx)
|
clear_context_callback(ctx)
|
||||||
|
|
||||||
|
|
||||||
def test_dc_close_events(tmpdir):
|
def test_dc_close_events():
|
||||||
ctx = ffi.gc(
|
ctx = capi.lib.dc_context_new(capi.lib.py_dc_callback, ffi.NULL, ffi.NULL)
|
||||||
capi.lib.dc_context_new(capi.lib.py_dc_callback, ffi.NULL, ffi.NULL),
|
|
||||||
lib.dc_context_unref,
|
|
||||||
)
|
|
||||||
evlog = EventLogger(ctx)
|
evlog = EventLogger(ctx)
|
||||||
evlog.set_timeout(5)
|
evlog.set_timeout(5)
|
||||||
set_context_callback(
|
set_context_callback(ctx, lambda ctx, evt_name, data1, data2: evlog(evt_name, data1, data2))
|
||||||
ctx,
|
|
||||||
lambda ctx, evt_name, data1, data2: evlog(evt_name, data1, data2)
|
|
||||||
)
|
|
||||||
p = tmpdir.join("hello.db")
|
|
||||||
lib.dc_open(ctx, p.strpath.encode("ascii"), ffi.NULL)
|
|
||||||
capi.lib.dc_close(ctx)
|
capi.lib.dc_close(ctx)
|
||||||
|
# test that we get events from dc_close
|
||||||
def find(info_string):
|
print(evlog.get_matching("DC_EVENT_INFO", check_error=False))
|
||||||
while 1:
|
print(evlog.get_matching("DC_EVENT_INFO", check_error=False))
|
||||||
ev = evlog.get_matching("DC_EVENT_INFO", check_error=False)
|
print(evlog.get_matching("DC_EVENT_INFO", check_error=False))
|
||||||
data2 = ev[2]
|
print(evlog.get_matching("DC_EVENT_INFO", check_error=False))
|
||||||
if info_string in data2:
|
|
||||||
return
|
|
||||||
else:
|
|
||||||
print("skipping event", *ev)
|
|
||||||
|
|
||||||
find("disconnecting INBOX-watch")
|
|
||||||
find("disconnecting sentbox-thread")
|
|
||||||
find("disconnecting mvbox-thread")
|
|
||||||
find("disconnecting SMTP")
|
|
||||||
find("Database closed")
|
|
||||||
|
|
||||||
|
|
||||||
def test_wrong_db(tmpdir):
|
def test_wrong_db(tmpdir):
|
||||||
dc_context = ffi.gc(
|
tmpdir.join("hello.db").write("123")
|
||||||
lib.dc_context_new(lib.py_dc_callback, ffi.NULL, ffi.NULL),
|
with pytest.raises(ValueError):
|
||||||
lib.dc_context_unref,
|
Account(db_path=tmpdir.strpath)
|
||||||
)
|
|
||||||
p = tmpdir.join("hello.db")
|
|
||||||
# write an invalid database file
|
|
||||||
p.write("x123" * 10)
|
|
||||||
assert not lib.dc_open(dc_context, p.strpath.encode("ascii"), ffi.NULL)
|
|
||||||
|
|
||||||
|
|
||||||
def test_event_defines():
|
def test_event_defines():
|
||||||
@@ -72,29 +49,3 @@ def test_sig():
|
|||||||
assert sig(const.DC_EVENT_SMTP_CONNECTED) == 2
|
assert sig(const.DC_EVENT_SMTP_CONNECTED) == 2
|
||||||
assert sig(const.DC_EVENT_IMAP_CONNECTED) == 2
|
assert sig(const.DC_EVENT_IMAP_CONNECTED) == 2
|
||||||
assert sig(const.DC_EVENT_SMTP_MESSAGE_SENT) == 2
|
assert sig(const.DC_EVENT_SMTP_MESSAGE_SENT) == 2
|
||||||
|
|
||||||
|
|
||||||
def test_markseen_invalid_message_ids(acfactory):
|
|
||||||
ac1 = acfactory.get_configured_offline_account()
|
|
||||||
contact1 = ac1.create_contact(email="some1@example.com", name="some1")
|
|
||||||
chat = ac1.create_chat_by_contact(contact1)
|
|
||||||
chat.send_text("one messae")
|
|
||||||
ac1._evlogger.get_matching("DC_EVENT_MSGS_CHANGED")
|
|
||||||
msg_ids = [9]
|
|
||||||
lib.dc_markseen_msgs(ac1._dc_context, msg_ids, len(msg_ids))
|
|
||||||
ac1._evlogger.ensure_event_not_queued("DC_EVENT_WARNING|DC_EVENT_ERROR")
|
|
||||||
|
|
||||||
|
|
||||||
def test_provider_info():
|
|
||||||
provider = lib.dc_provider_new_from_email(cutil.as_dc_charpointer("ex@example.com"))
|
|
||||||
assert cutil.from_dc_charpointer(
|
|
||||||
lib.dc_provider_get_overview_page(provider)
|
|
||||||
) == "https://providers.delta.chat/example.com"
|
|
||||||
assert cutil.from_dc_charpointer(lib.dc_provider_get_name(provider)) == "Example"
|
|
||||||
assert cutil.from_dc_charpointer(lib.dc_provider_get_markdown(provider)) == "\n..."
|
|
||||||
assert cutil.from_dc_charpointer(lib.dc_provider_get_status_date(provider)) == "2018-09"
|
|
||||||
assert lib.dc_provider_get_status(provider) == const.DC_PROVIDER_STATUS_PREPARATION
|
|
||||||
|
|
||||||
|
|
||||||
def test_provider_info_none():
|
|
||||||
assert lib.dc_provider_new_from_email(cutil.as_dc_charpointer("email@unexistent.no")) == ffi.NULL
|
|
||||||
|
|||||||
@@ -1,27 +0,0 @@
|
|||||||
import pytest
|
|
||||||
|
|
||||||
from deltachat import const
|
|
||||||
from deltachat import provider
|
|
||||||
|
|
||||||
|
|
||||||
def test_provider_info_from_email():
|
|
||||||
example = provider.Provider.from_email("email@example.com")
|
|
||||||
assert example.overview_page == "https://providers.delta.chat/example.com"
|
|
||||||
assert example.name == "Example"
|
|
||||||
assert example.markdown == "\n..."
|
|
||||||
assert example.status_date == "2018-09"
|
|
||||||
assert example.status == const.DC_PROVIDER_STATUS_PREPARATION
|
|
||||||
|
|
||||||
|
|
||||||
def test_provider_info_from_domain():
|
|
||||||
example = provider.Provider("example.com")
|
|
||||||
assert example.overview_page == "https://providers.delta.chat/example.com"
|
|
||||||
assert example.name == "Example"
|
|
||||||
assert example.markdown == "\n..."
|
|
||||||
assert example.status_date == "2018-09"
|
|
||||||
assert example.status == const.DC_PROVIDER_STATUS_PREPARATION
|
|
||||||
|
|
||||||
|
|
||||||
def test_provider_info_none():
|
|
||||||
with pytest.raises(provider.ProviderNotFoundError):
|
|
||||||
provider.Provider.from_email("email@unexistent.no")
|
|
||||||
@@ -1,30 +1,25 @@
|
|||||||
[tox]
|
[tox]
|
||||||
# make sure to update environment list in travis.yml and appveyor.yml
|
# make sure to update environment list in travis.yml and appveyor.yml
|
||||||
envlist =
|
envlist =
|
||||||
|
py27
|
||||||
py35
|
py35
|
||||||
lint
|
lint
|
||||||
auditwheels
|
auditwheels
|
||||||
|
|
||||||
[testenv]
|
[testenv]
|
||||||
commands =
|
commands =
|
||||||
pytest -v -rsXx {posargs:tests}
|
pytest -rsXx {posargs:tests}
|
||||||
python tests/package_wheels.py {toxworkdir}/wheelhouse
|
python tests/package_wheels.py {toxworkdir}/wheelhouse
|
||||||
passenv =
|
passenv =
|
||||||
TRAVIS
|
TRAVIS
|
||||||
DCC_RS_DEV
|
DCC_RS_DEV
|
||||||
DCC_RS_TARGET
|
|
||||||
DCC_PY_LIVECONFIG
|
|
||||||
deps =
|
deps =
|
||||||
pytest
|
pytest
|
||||||
pytest-rerunfailures
|
pytest-faulthandler
|
||||||
pytest-timeout
|
|
||||||
pytest-xdist
|
|
||||||
pdbpp
|
pdbpp
|
||||||
requests
|
|
||||||
|
|
||||||
[testenv:auditwheels]
|
[testenv:auditwheels]
|
||||||
skipsdist = True
|
skipsdist = True
|
||||||
deps = auditwheel
|
|
||||||
commands =
|
commands =
|
||||||
python tests/auditwheels.py {toxworkdir}/wheelhouse
|
python tests/auditwheels.py {toxworkdir}/wheelhouse
|
||||||
|
|
||||||
@@ -45,7 +40,7 @@ commands =
|
|||||||
[testenv:doc]
|
[testenv:doc]
|
||||||
basepython = python3.5
|
basepython = python3.5
|
||||||
deps =
|
deps =
|
||||||
sphinx==2.2.0
|
sphinx==2.0.1
|
||||||
breathe
|
breathe
|
||||||
|
|
||||||
changedir = doc
|
changedir = doc
|
||||||
@@ -54,12 +49,10 @@ commands =
|
|||||||
|
|
||||||
|
|
||||||
[pytest]
|
[pytest]
|
||||||
addopts = -v -rs --reruns 3 --reruns-delay 2
|
|
||||||
python_files = tests/test_*.py
|
python_files = tests/test_*.py
|
||||||
norecursedirs = .tox
|
norecursedirs = .tox
|
||||||
xfail_strict=true
|
xfail_strict=true
|
||||||
timeout = 60
|
timeout = 60
|
||||||
timeout_method = thread
|
|
||||||
|
|
||||||
[flake8]
|
[flake8]
|
||||||
max-line-length = 120
|
max-line-length = 120
|
||||||
|
|||||||
@@ -1,3 +0,0 @@
|
|||||||
pre-release-commit-message = "chore({{crate_name}}): release {{version}}"
|
|
||||||
pro-release-commit-message = "chore({{crate_name}}): starting development cycle for {{next_version}}"
|
|
||||||
no-dev-version = true
|
|
||||||
@@ -23,10 +23,11 @@ if [ $? != 0 ]; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
pushd python
|
pushd python
|
||||||
if [ -e "./liveconfig" && -z "$DCC_PY_LIVECONFIG" ]; then
|
toxargs="$@"
|
||||||
export DCC_PY_LIVECONFIG=liveconfig
|
if [ -e liveconfig ]; then
|
||||||
|
toxargs="--liveconfig liveconfig $@"
|
||||||
fi
|
fi
|
||||||
tox "$@"
|
tox $toxargs
|
||||||
ret=$?
|
ret=$?
|
||||||
popd
|
popd
|
||||||
exit $ret
|
exit $ret
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
nightly-2019-08-13
|
nightly-2019-06-16
|
||||||
|
|||||||
372
spec.md
372
spec.md
@@ -1,372 +0,0 @@
|
|||||||
# Chat-over-Email specification
|
|
||||||
|
|
||||||
Version 0.19.0
|
|
||||||
|
|
||||||
This document describes how emails can be used
|
|
||||||
to implement typical messenger functions
|
|
||||||
while staying compatible to existing MUAs.
|
|
||||||
|
|
||||||
- [Encryption](#encryption)
|
|
||||||
- [Outgoing messages](#outgoing-messages)
|
|
||||||
- [Incoming messages](#incoming-messages)
|
|
||||||
- [Forwarded messages](#forwarded-messages)
|
|
||||||
- [Groups](#groups)
|
|
||||||
- [Outgoing group messages](#outgoing-group-messages)
|
|
||||||
- [Incoming group messages](#incoming-group-messages)
|
|
||||||
- [Add and remove members](#add-and-remove-members)
|
|
||||||
- [Change group name](#change-group-name)
|
|
||||||
- [Set group image](#set-group-image)
|
|
||||||
- [Set profile image](#set-profile-image)
|
|
||||||
- [Miscellaneous](#miscellaneous)
|
|
||||||
|
|
||||||
|
|
||||||
# Encryption
|
|
||||||
|
|
||||||
Messages SHOULD be encrypted by the
|
|
||||||
[Autocrypt](https://autocrypt.org/level1.html) standard;
|
|
||||||
`prefer-encrypt=mutual` MAY be set by default.
|
|
||||||
|
|
||||||
Meta data (at least the subject and all chat-headers) SHOULD be encrypted
|
|
||||||
by the [Memoryhole](https://github.com/autocrypt/memoryhole) standard.
|
|
||||||
If Memoryhole is not used,
|
|
||||||
the subject of encrypted messages SHOULD be replaced by the string
|
|
||||||
`Chat: Encrypted message` where the part after the colon MAY be localized.
|
|
||||||
|
|
||||||
|
|
||||||
# Outgoing messages
|
|
||||||
|
|
||||||
Messengers MUST add a `Chat-Version: 1.0` header to outgoing messages.
|
|
||||||
For filtering and smart appearance of the messages in normal MUAs,
|
|
||||||
the `Subject` header SHOULD start with the characters `Chat:`
|
|
||||||
and SHOULD be an excerpt of the message.
|
|
||||||
Replies to messages MAY follow the typical `Re:`-format.
|
|
||||||
|
|
||||||
The body MAY contain text which MUST have the content type `text/plain`
|
|
||||||
or `mulipart/alternative` containing `text/plain`.
|
|
||||||
|
|
||||||
The text MAY be divided into a user-text-part and a footer-part using the
|
|
||||||
line `-- ` (minus, minus, space, lineend).
|
|
||||||
|
|
||||||
The user-text-part MUST contain only user generated content.
|
|
||||||
User generated content are eg. texts a user has actually typed
|
|
||||||
or pasted or forwarded from another user.
|
|
||||||
Full quotes, footers or sth. like that MUST NOT go to the user-text-part.
|
|
||||||
|
|
||||||
From: sender@domain
|
|
||||||
To: rcpt@domain
|
|
||||||
Chat-Version: 1.0
|
|
||||||
Content-Type: text/plain
|
|
||||||
Subject: Chat: Hello ...
|
|
||||||
|
|
||||||
Hello world!
|
|
||||||
|
|
||||||
|
|
||||||
# Incoming messages
|
|
||||||
|
|
||||||
The `Chat-Version` header MAY be used
|
|
||||||
to detect if a messages comes from a compatible messenger.
|
|
||||||
|
|
||||||
The `Subject` header MUST NOT be used
|
|
||||||
to detect compatible messengers, groups or whatever.
|
|
||||||
|
|
||||||
Messenger SHOULD show the `Subject`
|
|
||||||
if the message comes from a normal MUA together with the email-body.
|
|
||||||
The email-body SHOULD be converted
|
|
||||||
to plain text, full-quotes and similar regions SHOULD be cut.
|
|
||||||
|
|
||||||
Attachments SHOULD be shown where possible.
|
|
||||||
If an attachment cannot be shown, a non-distracting warning SHOULD be printed.
|
|
||||||
|
|
||||||
|
|
||||||
# Forwarded messages
|
|
||||||
|
|
||||||
Forwarded messages are outgoing messages that contain a forwarded-header
|
|
||||||
before the user generated content.
|
|
||||||
|
|
||||||
The forwarded header MUST contain two lines:
|
|
||||||
The first line contains the text
|
|
||||||
`---------- Forwarded message ----------`
|
|
||||||
(10 minus, space, text `Forwarded message`, space, 10 minus).
|
|
||||||
The second line starts with `From: ` followed by the original sender
|
|
||||||
which SHOULD be anonymized or just a placeholder.
|
|
||||||
|
|
||||||
From: sender@domain
|
|
||||||
To: rcpt@domain
|
|
||||||
Chat-Version: 1.0
|
|
||||||
Content-Type: text/plain
|
|
||||||
Subject: Chat: Forwarded message
|
|
||||||
|
|
||||||
---------- Forwarded message ----------
|
|
||||||
From: Messenger
|
|
||||||
|
|
||||||
Hello world!
|
|
||||||
|
|
||||||
Incoming forwarded messages are detected by the header.
|
|
||||||
The messenger SHOULD mark these messages in a way that
|
|
||||||
it becomes obvious that the message is not created by the sender.
|
|
||||||
Note that most messengers do not show the original sender of forwarded messages
|
|
||||||
but MUAs typically expose the sender in the UI.
|
|
||||||
|
|
||||||
|
|
||||||
# Groups
|
|
||||||
|
|
||||||
Groups are chats with usually more than one recipient,
|
|
||||||
each defined by an email-address.
|
|
||||||
The sender plus the recipients are the group members.
|
|
||||||
|
|
||||||
To allow different groups with the same members,
|
|
||||||
groups are identified by a group-id.
|
|
||||||
The group-id MUST be created only from the characters
|
|
||||||
`0`-`9`, `A`-`Z`, `a`-`z` `_` and `-`.
|
|
||||||
|
|
||||||
Groups MUST have a group-name.
|
|
||||||
The group-name is any non-zero-length UTF-8 string.
|
|
||||||
|
|
||||||
Groups MAY have a group-image.
|
|
||||||
|
|
||||||
|
|
||||||
## Outgoing groups messages
|
|
||||||
|
|
||||||
All group members MUST be added to the `From`/`To` headers.
|
|
||||||
The group-id MUST be written to the `Chat-Group-ID` header.
|
|
||||||
The group-name MUST be written to `Chat-Group-Name` header
|
|
||||||
(the forced presence of this header makes it easier
|
|
||||||
to join a group chat on a second device any time).
|
|
||||||
|
|
||||||
The `Subject` header of outgoing group messages
|
|
||||||
SHOULD start with the characters `Chat:`
|
|
||||||
followed by the group-name and a colon followed by an excerpt of the message.
|
|
||||||
|
|
||||||
To identify the group-id on replies from normal MUAs,
|
|
||||||
the group-id MUST also be added to the message-id of outgoing messages.
|
|
||||||
The message-id MUST have the format `Gr.<group-id>.<unique data>`.
|
|
||||||
|
|
||||||
From: member1@domain
|
|
||||||
To: member2@domain, member3@domain
|
|
||||||
Chat-Version: 1.0
|
|
||||||
Chat-Group-ID: 1234xyZ
|
|
||||||
Chat-Group-Name: My Group
|
|
||||||
Message-ID: Gr.1234xyZ.0001@domain
|
|
||||||
Subject: Chat: My Group: Hello group ...
|
|
||||||
|
|
||||||
Hello group - this group contains three members
|
|
||||||
|
|
||||||
Messengers adding the member list in the form `Name <email-address>`
|
|
||||||
MUST take care only to spread the names authorized by the contacts themselves.
|
|
||||||
Otherwise, names as _Daddy_ or _Honey_ may be spread
|
|
||||||
(this issue is also true for normal MUAs, however,
|
|
||||||
for more contact- and chat-centralized apps
|
|
||||||
such situations happen more frequently).
|
|
||||||
|
|
||||||
|
|
||||||
## Incoming group messages
|
|
||||||
|
|
||||||
The messenger MUST search incoming messages for the group-id
|
|
||||||
in the following headers: `Chat-Group-ID`,
|
|
||||||
`Message-ID`, `In-Reply-To` and `References` (in this order).
|
|
||||||
|
|
||||||
If the messenger finds a valid and existent group-id,
|
|
||||||
the message SHOULD be assigned to the given group.
|
|
||||||
If the messenger finds a valid but not existent group-id,
|
|
||||||
the messenger MAY create a new group.
|
|
||||||
If no group-id is found,
|
|
||||||
the message MAY be assigned
|
|
||||||
to a normal single-user chat with the email-address given in `From`.
|
|
||||||
|
|
||||||
|
|
||||||
## Add and remove members
|
|
||||||
|
|
||||||
Messenger clients MUST construct the member list
|
|
||||||
from the `From`/`To` headers only on the first group message
|
|
||||||
or if they see a `Chat-Group-Member-Added`
|
|
||||||
or `Chat-Group-Member-Removed` action header.
|
|
||||||
Both headers MUST have the email-address
|
|
||||||
of the added or removed member as the value.
|
|
||||||
Messenger clients MUST NOT construct the member list
|
|
||||||
on other group messages
|
|
||||||
(this is to avoid accidentally altered To-lists in normal MUAs;
|
|
||||||
the user does not expect adding a user to a _message_
|
|
||||||
will also add him to the _group_ "forever").
|
|
||||||
|
|
||||||
The messenger SHOULD send an explicit mail for each added or removed member.
|
|
||||||
The body of the message SHOULD contain
|
|
||||||
a localized description about what happened
|
|
||||||
and the message SHOULD appear as a message or action from the sender.
|
|
||||||
|
|
||||||
From: member1@domain
|
|
||||||
To: member2@domain, member3@domain, member4@domain
|
|
||||||
Chat-Version: 1.0
|
|
||||||
Chat-Group-ID: 1234xyZ
|
|
||||||
Chat-Group-Name: My Group
|
|
||||||
Chat-Group-Member-Added: member4@domain
|
|
||||||
Message-ID: Gr.1234xyZ.0002@domain
|
|
||||||
Subject: Chat: My Group: Hello, ...
|
|
||||||
|
|
||||||
Hello, I've added member4@domain to our group. Now we have 4 members.
|
|
||||||
|
|
||||||
To remove a member:
|
|
||||||
|
|
||||||
From: member1@domain
|
|
||||||
To: member2@domain, member3@domain
|
|
||||||
Chat-Version: 1.0
|
|
||||||
Chat-Group-ID: 1234xyZ
|
|
||||||
Chat-Group-Name: My Group
|
|
||||||
Chat-Group-Member-Removed: member4@domain
|
|
||||||
Message-ID: Gr.1234xyZ.0003@domain
|
|
||||||
Subject: Chat: My Group: Hello, ...
|
|
||||||
|
|
||||||
Hello, I've removed member4@domain from our group. Now we have 3 members.
|
|
||||||
|
|
||||||
|
|
||||||
## Change group name
|
|
||||||
|
|
||||||
To change the group-name,
|
|
||||||
the messenger MUST send the action header `Chat-Group-Name-Changed`
|
|
||||||
with the value set to the old group name to all group members.
|
|
||||||
The new group name goes to the header `Chat-Group-Name`.
|
|
||||||
|
|
||||||
The messenger SHOULD send an explicit mail for each name change.
|
|
||||||
The body of the message SHOULD contain
|
|
||||||
a localized description about what happened
|
|
||||||
and the message SHOULD appear as a message or action from the sender.
|
|
||||||
|
|
||||||
From: member1@domain
|
|
||||||
To: member2@domain, member3@domain
|
|
||||||
Chat-Version: 1.0
|
|
||||||
Chat-Group-ID: 1234xyZ
|
|
||||||
Chat-Group-Name: Our Group
|
|
||||||
Chat-Group-Name-Changed: My Group
|
|
||||||
Message-ID: Gr.1234xyZ.0004@domain
|
|
||||||
Subject: Chat: Our Group: Hello, ...
|
|
||||||
|
|
||||||
Hello, I've changed the group name from "My Group" to "Our Group".
|
|
||||||
|
|
||||||
|
|
||||||
## Set group image
|
|
||||||
|
|
||||||
A group MAY have a group-image.
|
|
||||||
To change or set the group-image,
|
|
||||||
the messenger MUST attach an image file to a message
|
|
||||||
and MUST add the header `Chat-Group-Image`
|
|
||||||
with the value set to the image name.
|
|
||||||
|
|
||||||
To remove the group-image,
|
|
||||||
the messenger MUST add the header `Chat-Group-Image: 0`.
|
|
||||||
|
|
||||||
The messenger SHOULD send an explicit mail for each group image change.
|
|
||||||
The body of the message SHOULD contain
|
|
||||||
a localized description about what happened
|
|
||||||
and the message SHOULD appear as a message or action from the sender.
|
|
||||||
|
|
||||||
|
|
||||||
From: member1@domain
|
|
||||||
To: member2@domain, member3@domain
|
|
||||||
Chat-Version: 1.0
|
|
||||||
Chat-Group-ID: 1234xyZ
|
|
||||||
Chat-Group-Name: Our Group
|
|
||||||
Chat-Group-Image: image.jpg
|
|
||||||
Message-ID: Gr.1234xyZ.0005@domain
|
|
||||||
Subject: Chat: Our Group: Hello, ...
|
|
||||||
Content-Type: multipart/mixed; boundary="==break=="
|
|
||||||
|
|
||||||
--==break==
|
|
||||||
Content-Type: text/plain
|
|
||||||
|
|
||||||
Hello, I've changed the group image.
|
|
||||||
--==break==
|
|
||||||
Content-Type: image/jpeg
|
|
||||||
Content-Disposition: attachment; filename="image.jpg"
|
|
||||||
|
|
||||||
/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAYEBQYFBAYGBQYHBw ...
|
|
||||||
--==break==--
|
|
||||||
|
|
||||||
The image format SHOULD be image/jpeg or image/png.
|
|
||||||
To save data, it is RECOMMENDED
|
|
||||||
to add a `Chat-Group-Image` only on image changes.
|
|
||||||
|
|
||||||
|
|
||||||
# Set profile image
|
|
||||||
|
|
||||||
A user MAY have a profile-image that MAY be spread to his contacts.
|
|
||||||
To change or set the profile-image,
|
|
||||||
the messenger MUST attach an image file to a message
|
|
||||||
and MUST add the header `Chat-Profile-Image`
|
|
||||||
with the value set to the image name.
|
|
||||||
|
|
||||||
To remove the profile-image,
|
|
||||||
the messenger MUST add the header `Chat-Profile-Image: 0`.
|
|
||||||
|
|
||||||
To spread the image,
|
|
||||||
the messenger MAY send the profile image
|
|
||||||
together with the next mail to a given contact
|
|
||||||
(to do this only once,
|
|
||||||
the messenger has to keep a `profile_image_update_state` somewhere).
|
|
||||||
Alternatively, the messenger MAY send an explicit mail
|
|
||||||
for each profile-image change to all contacts using a compatible messenger.
|
|
||||||
The messenger SHOULD NOT send an explicit mail to normal MUAs.
|
|
||||||
|
|
||||||
From: sender@domain
|
|
||||||
To: rcpt@domain
|
|
||||||
Chat-Version: 1.0
|
|
||||||
Chat-Profile-Image: photo.jpg
|
|
||||||
Subject: Chat: Hello, ...
|
|
||||||
Content-Type: multipart/mixed; boundary="==break=="
|
|
||||||
|
|
||||||
--==break==
|
|
||||||
Content-Type: text/plain
|
|
||||||
|
|
||||||
Hello, I've changed my profile image.
|
|
||||||
--==break==
|
|
||||||
Content-Type: image/jpeg
|
|
||||||
Content-Disposition: attachment; filename="photo.jpg"
|
|
||||||
|
|
||||||
AKCgkJi3j4l5kjoldfUAKCgkJi3j4lldfHjgWICwgIEBQYFBA ...
|
|
||||||
--==break==--
|
|
||||||
|
|
||||||
The image format SHOULD be image/jpeg or image/png.
|
|
||||||
Note that `Chat-Profile-Image` may appear together with all other headers,
|
|
||||||
eg. there may be a `Chat-Profile-Image` and a `Chat-Group-Image` header
|
|
||||||
in the same message.
|
|
||||||
To save data, it is RECOMMENDED to add a `Chat-Profile-Image` header
|
|
||||||
only on image changes.
|
|
||||||
|
|
||||||
|
|
||||||
# Miscellaneous
|
|
||||||
|
|
||||||
Messengers SHOULD use the header `Chat-Predecessor`
|
|
||||||
instead of `In-Reply-To` as the latter one results
|
|
||||||
in infinite threads on typical MUAs.
|
|
||||||
|
|
||||||
Messengers SHOULD add a `Chat-Voice-message: 1` header
|
|
||||||
if an attached audio file is a voice message.
|
|
||||||
|
|
||||||
Messengers MAY add a `Chat-Duration` header
|
|
||||||
to specify the duration of attached audio or video files.
|
|
||||||
The value MUST be the duration in milliseconds.
|
|
||||||
This allows the receiver to show the time without knowing the file format.
|
|
||||||
|
|
||||||
Chat-Predecessor: foo123@domain
|
|
||||||
Chat-Voice-Message: 1
|
|
||||||
Chat-Duration: 10000
|
|
||||||
|
|
||||||
Messengers MAY send and receive Message Disposition Notifications
|
|
||||||
(MDNs, [RFC 8098](https://tools.ietf.org/html/rfc8098),
|
|
||||||
[RFC 3503](https://tools.ietf.org/html/rfc3503))
|
|
||||||
using the `Chat-Disposition-Notification-To` header
|
|
||||||
instead of the `Disposition-Notification-To`
|
|
||||||
(which unfortunately forces many other MUAs
|
|
||||||
to send weird mails not following any standard).
|
|
||||||
|
|
||||||
|
|
||||||
## Sync messages
|
|
||||||
|
|
||||||
If some action is required by a message header,
|
|
||||||
the action should only be performed if the _effective date_ is newer
|
|
||||||
than the date the last action was performed.
|
|
||||||
|
|
||||||
We define the effective date of a message
|
|
||||||
as the sending time of the message as indicated by its Date header,
|
|
||||||
or the time of first receipt if that date is in the future or unavailable.
|
|
||||||
|
|
||||||
|
|
||||||
Copyright © 2017-2019 Delta Chat contributors.
|
|
||||||
@@ -1,12 +1,12 @@
|
|||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
use std::ffi::CStr;
|
use std::ffi::{CStr, CString};
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use std::{fmt, str};
|
use std::{fmt, str};
|
||||||
|
|
||||||
use mmime::mailimf_types::*;
|
use mmime::mailimf_types::*;
|
||||||
|
|
||||||
use crate::constants::*;
|
use crate::constants::*;
|
||||||
use crate::contact::*;
|
use crate::dc_contact::*;
|
||||||
use crate::key::*;
|
use crate::key::*;
|
||||||
|
|
||||||
/// Possible values for encryption preference
|
/// Possible values for encryption preference
|
||||||
@@ -63,8 +63,11 @@ impl Aheader {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn from_imffields(wanted_from: &str, header: *const mailimf_fields) -> Option<Self> {
|
pub fn from_imffields(
|
||||||
if header.is_null() {
|
wanted_from: *const libc::c_char,
|
||||||
|
header: *const mailimf_fields,
|
||||||
|
) -> Option<Self> {
|
||||||
|
if wanted_from.is_null() || header.is_null() {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -90,7 +93,9 @@ impl Aheader {
|
|||||||
|
|
||||||
match Self::from_str(value) {
|
match Self::from_str(value) {
|
||||||
Ok(test) => {
|
Ok(test) => {
|
||||||
if addr_cmp(&test.addr, wanted_from) {
|
// TODO: implement rust-safe version of dc_addr_cmp
|
||||||
|
let addr = CString::new(test.addr.clone()).unwrap();
|
||||||
|
if unsafe { dc_addr_cmp(addr.as_ptr(), wanted_from) } == 0 {
|
||||||
if fine_header.is_none() {
|
if fine_header.is_none() {
|
||||||
fine_header = Some(test);
|
fine_header = Some(test);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
1922
src/chat.rs
1922
src/chat.rs
File diff suppressed because it is too large
Load Diff
328
src/chatlist.rs
328
src/chatlist.rs
@@ -1,328 +0,0 @@
|
|||||||
use crate::chat::*;
|
|
||||||
use crate::constants::*;
|
|
||||||
use crate::contact::*;
|
|
||||||
use crate::context::*;
|
|
||||||
use crate::error::Result;
|
|
||||||
use crate::lot::Lot;
|
|
||||||
use crate::message::*;
|
|
||||||
use crate::stock::StockMessage;
|
|
||||||
|
|
||||||
/// An object representing a single chatlist in memory.
|
|
||||||
///
|
|
||||||
/// Chatlist objects contain chat IDs and, if possible, message IDs belonging to them.
|
|
||||||
/// The chatlist object is not updated; if you want an update, you have to recreate the object.
|
|
||||||
///
|
|
||||||
/// For a **typical chat overview**, the idea is to get the list of all chats via dc_get_chatlist()
|
|
||||||
/// without any listflags (see below) and to implement a "virtual list" or so
|
|
||||||
/// (the count of chats is known by chatlist.len()).
|
|
||||||
///
|
|
||||||
/// Only for the items that are in view (the list may have several hundreds chats),
|
|
||||||
/// the UI should call chatlist.get_summary() then.
|
|
||||||
/// chatlist.get_summary() provides all elements needed for painting the item.
|
|
||||||
///
|
|
||||||
/// On a click of such an item, the UI should change to the chat view
|
|
||||||
/// and get all messages from this view via dc_get_chat_msgs().
|
|
||||||
/// Again, a "virtual list" is created (the count of messages is known)
|
|
||||||
/// and for each messages that is scrolled into view, dc_get_msg() is called then.
|
|
||||||
///
|
|
||||||
/// Why no listflags?
|
|
||||||
/// Without listflags, dc_get_chatlist() adds the deaddrop and the archive "link" automatically as needed.
|
|
||||||
/// The UI can just render these items differently then. Although the deaddrop link is currently always the
|
|
||||||
/// first entry and only present on new messages, there is the rough idea that it can be optionally always
|
|
||||||
/// present and sorted into the list by date. Rendering the deaddrop in the described way
|
|
||||||
/// would not add extra work in the UI then.
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct Chatlist {
|
|
||||||
/// Stores pairs of `chat_id, message_id`
|
|
||||||
ids: Vec<(u32, u32)>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Chatlist {
|
|
||||||
/// Get a list of chats.
|
|
||||||
/// The list can be filtered by query parameters.
|
|
||||||
///
|
|
||||||
/// The list is already sorted and starts with the most recent chat in use.
|
|
||||||
/// The sorting takes care of invalid sending dates, drafts and chats without messages.
|
|
||||||
/// Clients should not try to re-sort the list as this would be an expensive action
|
|
||||||
/// and would result in inconsistencies between clients.
|
|
||||||
///
|
|
||||||
/// To get information about each entry, use eg. chatlist.get_summary().
|
|
||||||
///
|
|
||||||
/// By default, the function adds some special entries to the list.
|
|
||||||
/// These special entries can be identified by the ID returned by chatlist.get_chat_id():
|
|
||||||
/// - DC_CHAT_ID_DEADDROP (1) - this special chat is present if there are
|
|
||||||
/// messages from addresses that have no relationship to the configured account.
|
|
||||||
/// The last of these messages is represented by DC_CHAT_ID_DEADDROP and you can retrieve details
|
|
||||||
/// about it with chatlist.get_msg_id(). Typically, the UI asks the user "Do you want to chat with NAME?"
|
|
||||||
/// and offers the options "Yes" (call dc_create_chat_by_msg_id()), "Never" (call dc_block_contact())
|
|
||||||
/// or "Not now".
|
|
||||||
/// The UI can also offer a "Close" button that calls dc_marknoticed_contact() then.
|
|
||||||
/// - DC_CHAT_ID_ARCHIVED_LINK (6) - this special chat is present if the user has
|
|
||||||
/// archived _any_ chat using dc_archive_chat(). The UI should show a link as
|
|
||||||
/// "Show archived chats", if the user clicks this item, the UI should show a
|
|
||||||
/// list of all archived chats that can be created by this function hen using
|
|
||||||
/// the DC_GCL_ARCHIVED_ONLY flag.
|
|
||||||
/// - DC_CHAT_ID_ALLDONE_HINT (7) - this special chat is present
|
|
||||||
/// if DC_GCL_ADD_ALLDONE_HINT is added to listflags
|
|
||||||
/// and if there are only archived chats.
|
|
||||||
///
|
|
||||||
/// The `listflags` is a combination of flags:
|
|
||||||
/// - if the flag DC_GCL_ARCHIVED_ONLY is set, only archived chats are returned.
|
|
||||||
/// if DC_GCL_ARCHIVED_ONLY is not set, only unarchived chats are returned and
|
|
||||||
/// the pseudo-chat DC_CHAT_ID_ARCHIVED_LINK is added if there are _any_ archived
|
|
||||||
/// chats
|
|
||||||
/// - if the flag DC_GCL_NO_SPECIALS is set, deaddrop and archive link are not added
|
|
||||||
/// to the list (may be used eg. for selecting chats on forwarding, the flag is
|
|
||||||
/// not needed when DC_GCL_ARCHIVED_ONLY is already set)
|
|
||||||
/// - if the flag DC_GCL_ADD_ALLDONE_HINT is set, DC_CHAT_ID_ALLDONE_HINT
|
|
||||||
/// is added as needed.
|
|
||||||
/// `query`: An optional query for filtering the list. Only chats matching this query
|
|
||||||
/// are returned.
|
|
||||||
/// `query_contact_id`: An optional contact ID for filtering the list. Only chats including this contact ID
|
|
||||||
/// are returned.
|
|
||||||
pub fn try_load(
|
|
||||||
context: &Context,
|
|
||||||
listflags: usize,
|
|
||||||
query: Option<&str>,
|
|
||||||
query_contact_id: Option<u32>,
|
|
||||||
) -> Result<Self> {
|
|
||||||
let mut add_archived_link_item = 0;
|
|
||||||
|
|
||||||
// select with left join and minimum:
|
|
||||||
// - the inner select must use `hidden` and _not_ `m.hidden`
|
|
||||||
// which would refer the outer select and take a lot of time
|
|
||||||
// - `GROUP BY` is needed several messages may have the same timestamp
|
|
||||||
// - the list starts with the newest chats
|
|
||||||
// nb: the query currently shows messages from blocked contacts in groups.
|
|
||||||
// however, for normal-groups, this is okay as the message is also returned by dc_get_chat_msgs()
|
|
||||||
// (otherwise it would be hard to follow conversations, wa and tg do the same)
|
|
||||||
// for the deaddrop, however, they should really be hidden, however, _currently_ the deaddrop is not
|
|
||||||
// shown at all permanent in the chatlist.
|
|
||||||
|
|
||||||
let process_row = |row: &rusqlite::Row| {
|
|
||||||
let chat_id: i32 = row.get(0)?;
|
|
||||||
// TODO: verify that it is okay for this to be Null
|
|
||||||
let msg_id: i32 = row.get(1).unwrap_or_default();
|
|
||||||
|
|
||||||
Ok((chat_id as u32, msg_id as u32))
|
|
||||||
};
|
|
||||||
|
|
||||||
let process_rows = |rows: rusqlite::MappedRows<_>| {
|
|
||||||
rows.collect::<std::result::Result<Vec<_>, _>>()
|
|
||||||
.map_err(Into::into)
|
|
||||||
};
|
|
||||||
|
|
||||||
// nb: the query currently shows messages from blocked contacts in groups.
|
|
||||||
// however, for normal-groups, this is okay as the message is also returned by dc_get_chat_msgs()
|
|
||||||
// (otherwise it would be hard to follow conversations, wa and tg do the same)
|
|
||||||
// for the deaddrop, however, they should really be hidden, however, _currently_ the deaddrop is not
|
|
||||||
// shown at all permanent in the chatlist.
|
|
||||||
|
|
||||||
let mut ids = if let Some(query_contact_id) = query_contact_id {
|
|
||||||
// show chats shared with a given contact
|
|
||||||
context.sql.query_map(
|
|
||||||
"SELECT c.id, m.id FROM chats c LEFT JOIN msgs m \
|
|
||||||
ON c.id=m.chat_id \
|
|
||||||
AND m.timestamp=( SELECT MAX(timestamp) \
|
|
||||||
FROM msgs WHERE chat_id=c.id \
|
|
||||||
AND (hidden=0 OR (hidden=1 AND state=19))) WHERE c.id>9 \
|
|
||||||
AND c.blocked=0 AND c.id IN(SELECT chat_id FROM chats_contacts WHERE contact_id=?) \
|
|
||||||
GROUP BY c.id ORDER BY IFNULL(m.timestamp,0) DESC, m.id DESC;",
|
|
||||||
params![query_contact_id as i32],
|
|
||||||
process_row,
|
|
||||||
process_rows,
|
|
||||||
)?
|
|
||||||
} else if 0 != listflags & DC_GCL_ARCHIVED_ONLY {
|
|
||||||
// show archived chats
|
|
||||||
context.sql.query_map(
|
|
||||||
"SELECT c.id, m.id FROM chats c LEFT JOIN msgs m \
|
|
||||||
ON c.id=m.chat_id \
|
|
||||||
AND m.timestamp=( SELECT MAX(timestamp) \
|
|
||||||
FROM msgs WHERE chat_id=c.id \
|
|
||||||
AND (hidden=0 OR (hidden=1 AND state=19))) WHERE c.id>9 \
|
|
||||||
AND c.blocked=0 AND c.archived=1 GROUP BY c.id \
|
|
||||||
ORDER BY IFNULL(m.timestamp,0) DESC, m.id DESC;",
|
|
||||||
params![],
|
|
||||||
process_row,
|
|
||||||
process_rows,
|
|
||||||
)?
|
|
||||||
} else if let Some(query) = query {
|
|
||||||
let query = query.trim().to_string();
|
|
||||||
ensure!(!query.is_empty(), "missing query");
|
|
||||||
|
|
||||||
let str_like_cmd = format!("%{}%", query);
|
|
||||||
context.sql.query_map(
|
|
||||||
"SELECT c.id, m.id FROM chats c LEFT JOIN msgs m \
|
|
||||||
ON c.id=m.chat_id \
|
|
||||||
AND m.timestamp=( SELECT MAX(timestamp) \
|
|
||||||
FROM msgs WHERE chat_id=c.id \
|
|
||||||
AND (hidden=0 OR (hidden=1 AND state=19))) WHERE c.id>9 \
|
|
||||||
AND c.blocked=0 AND c.name LIKE ? \
|
|
||||||
GROUP BY c.id ORDER BY IFNULL(m.timestamp,0) DESC, m.id DESC;",
|
|
||||||
params![str_like_cmd],
|
|
||||||
process_row,
|
|
||||||
process_rows,
|
|
||||||
)?
|
|
||||||
} else {
|
|
||||||
// show normal chatlist
|
|
||||||
let mut ids = context.sql.query_map(
|
|
||||||
"SELECT c.id, m.id FROM chats c \
|
|
||||||
LEFT JOIN msgs m \
|
|
||||||
ON c.id=m.chat_id \
|
|
||||||
AND m.timestamp=( SELECT MAX(timestamp) \
|
|
||||||
FROM msgs WHERE chat_id=c.id \
|
|
||||||
AND (hidden=0 OR (hidden=1 AND state=19))) WHERE c.id>9 \
|
|
||||||
AND c.blocked=0 AND c.archived=0 \
|
|
||||||
GROUP BY c.id \
|
|
||||||
ORDER BY IFNULL(m.timestamp,0) DESC, m.id DESC;",
|
|
||||||
params![],
|
|
||||||
process_row,
|
|
||||||
process_rows,
|
|
||||||
)?;
|
|
||||||
if 0 == listflags & DC_GCL_NO_SPECIALS {
|
|
||||||
let last_deaddrop_fresh_msg_id = get_last_deaddrop_fresh_msg(context);
|
|
||||||
if last_deaddrop_fresh_msg_id > 0 {
|
|
||||||
ids.push((1, last_deaddrop_fresh_msg_id));
|
|
||||||
}
|
|
||||||
add_archived_link_item = 1;
|
|
||||||
}
|
|
||||||
ids
|
|
||||||
};
|
|
||||||
|
|
||||||
if 0 != add_archived_link_item && dc_get_archived_cnt(context) > 0 {
|
|
||||||
if ids.is_empty() && 0 != listflags & DC_GCL_ADD_ALLDONE_HINT {
|
|
||||||
ids.push((DC_CHAT_ID_ALLDONE_HINT, 0));
|
|
||||||
}
|
|
||||||
ids.push((DC_CHAT_ID_ARCHIVED_LINK, 0));
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(Chatlist { ids })
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Find out the number of chats.
|
|
||||||
pub fn len(&self) -> usize {
|
|
||||||
self.ids.len()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_empty(&self) -> bool {
|
|
||||||
self.ids.is_empty()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get a single chat ID of a chatlist.
|
|
||||||
///
|
|
||||||
/// To get the message object from the message ID, use dc_get_chat().
|
|
||||||
pub fn get_chat_id(&self, index: usize) -> u32 {
|
|
||||||
if index >= self.ids.len() {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
self.ids[index].0
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get a single message ID of a chatlist.
|
|
||||||
///
|
|
||||||
/// To get the message object from the message ID, use dc_get_msg().
|
|
||||||
pub fn get_msg_id(&self, index: usize) -> u32 {
|
|
||||||
if index >= self.ids.len() {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.ids[index].1
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get a summary for a chatlist index.
|
|
||||||
///
|
|
||||||
/// The summary is returned by a dc_lot_t object with the following fields:
|
|
||||||
///
|
|
||||||
/// - dc_lot_t::text1: contains the username or the strings "Me", "Draft" and so on.
|
|
||||||
/// The string may be colored by having a look at text1_meaning.
|
|
||||||
/// If there is no such name or it should not be displayed, the element is NULL.
|
|
||||||
/// - dc_lot_t::text1_meaning: one of DC_TEXT1_USERNAME, DC_TEXT1_SELF or DC_TEXT1_DRAFT.
|
|
||||||
/// Typically used to show dc_lot_t::text1 with different colors. 0 if not applicable.
|
|
||||||
/// - dc_lot_t::text2: contains an excerpt of the message text or strings as
|
|
||||||
/// "No messages". May be NULL of there is no such text (eg. for the archive link)
|
|
||||||
/// - dc_lot_t::timestamp: the timestamp of the message. 0 if not applicable.
|
|
||||||
/// - dc_lot_t::state: The state of the message as one of the DC_STATE_* constants (see #dc_msg_get_state()).
|
|
||||||
// 0 if not applicable.
|
|
||||||
pub fn get_summary(&self, context: &Context, index: usize, chat: Option<&Chat>) -> Lot {
|
|
||||||
// The summary is created by the chat, not by the last message.
|
|
||||||
// This is because we may want to display drafts here or stuff as
|
|
||||||
// "is typing".
|
|
||||||
// Also, sth. as "No messages" would not work if the summary comes from a message.
|
|
||||||
|
|
||||||
let mut ret = Lot::new();
|
|
||||||
if index >= self.ids.len() {
|
|
||||||
ret.text2 = Some("ErrBadChatlistIndex".to_string());
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
let chat_loaded: Chat;
|
|
||||||
let chat = if let Some(chat) = chat {
|
|
||||||
chat
|
|
||||||
} else {
|
|
||||||
if let Ok(chat) = Chat::load_from_db(context, self.ids[index].0) {
|
|
||||||
chat_loaded = chat;
|
|
||||||
&chat_loaded
|
|
||||||
} else {
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let lastmsg_id = self.ids[index].1;
|
|
||||||
let mut lastcontact = None;
|
|
||||||
|
|
||||||
let lastmsg = if 0 != lastmsg_id {
|
|
||||||
if let Ok(lastmsg) = dc_msg_load_from_db(context, lastmsg_id) {
|
|
||||||
if lastmsg.from_id != 1 as libc::c_uint
|
|
||||||
&& (chat.typ == Chattype::Group || chat.typ == Chattype::VerifiedGroup)
|
|
||||||
{
|
|
||||||
lastcontact = Contact::load_from_db(context, lastmsg.from_id).ok();
|
|
||||||
}
|
|
||||||
|
|
||||||
Some(lastmsg)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
|
|
||||||
if chat.id == DC_CHAT_ID_ARCHIVED_LINK {
|
|
||||||
ret.text2 = None;
|
|
||||||
} else if lastmsg.is_none() || lastmsg.as_ref().unwrap().from_id == DC_CONTACT_ID_UNDEFINED
|
|
||||||
{
|
|
||||||
ret.text2 = Some(context.stock_str(StockMessage::NoMessages).to_string());
|
|
||||||
} else {
|
|
||||||
ret.fill(&mut lastmsg.unwrap(), chat, lastcontact.as_ref(), context);
|
|
||||||
}
|
|
||||||
|
|
||||||
ret
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn dc_get_archived_cnt(context: &Context) -> u32 {
|
|
||||||
context
|
|
||||||
.sql
|
|
||||||
.query_get_value(
|
|
||||||
context,
|
|
||||||
"SELECT COUNT(*) FROM chats WHERE blocked=0 AND archived=1;",
|
|
||||||
params![],
|
|
||||||
)
|
|
||||||
.unwrap_or_default()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_last_deaddrop_fresh_msg(context: &Context) -> u32 {
|
|
||||||
// We have an index over the state-column, this should be sufficient as there are typically
|
|
||||||
// only few fresh messages.
|
|
||||||
context
|
|
||||||
.sql
|
|
||||||
.query_get_value(
|
|
||||||
context,
|
|
||||||
"SELECT m.id FROM msgs m LEFT JOIN chats c ON c.id=m.chat_id \
|
|
||||||
WHERE m.state=10 \
|
|
||||||
AND m.hidden=0 \
|
|
||||||
AND c.blocked=2 \
|
|
||||||
ORDER BY m.timestamp DESC, m.id DESC;",
|
|
||||||
params![],
|
|
||||||
)
|
|
||||||
.unwrap_or_default()
|
|
||||||
}
|
|
||||||
168
src/config.rs
168
src/config.rs
@@ -1,168 +0,0 @@
|
|||||||
use strum::{EnumProperty, IntoEnumIterator};
|
|
||||||
use strum_macros::{AsRefStr, Display, EnumIter, EnumProperty, EnumString};
|
|
||||||
|
|
||||||
use crate::constants::DC_VERSION_STR;
|
|
||||||
use crate::context::Context;
|
|
||||||
use crate::dc_tools::*;
|
|
||||||
use crate::error::Error;
|
|
||||||
use crate::job::*;
|
|
||||||
use crate::stock::StockMessage;
|
|
||||||
|
|
||||||
/// The available configuration keys.
|
|
||||||
#[derive(
|
|
||||||
Debug, Clone, Copy, PartialEq, Eq, Display, EnumString, AsRefStr, EnumIter, EnumProperty,
|
|
||||||
)]
|
|
||||||
#[strum(serialize_all = "snake_case")]
|
|
||||||
pub enum Config {
|
|
||||||
Addr,
|
|
||||||
MailServer,
|
|
||||||
MailUser,
|
|
||||||
MailPw,
|
|
||||||
MailPort,
|
|
||||||
SendServer,
|
|
||||||
SendUser,
|
|
||||||
SendPw,
|
|
||||||
SendPort,
|
|
||||||
ServerFlags,
|
|
||||||
#[strum(props(default = "INBOX"))]
|
|
||||||
ImapFolder,
|
|
||||||
Displayname,
|
|
||||||
Selfstatus,
|
|
||||||
Selfavatar,
|
|
||||||
#[strum(props(default = "1"))]
|
|
||||||
E2eeEnabled,
|
|
||||||
#[strum(props(default = "1"))]
|
|
||||||
MdnsEnabled,
|
|
||||||
#[strum(props(default = "1"))]
|
|
||||||
InboxWatch,
|
|
||||||
#[strum(props(default = "1"))]
|
|
||||||
SentboxWatch,
|
|
||||||
#[strum(props(default = "1"))]
|
|
||||||
MvboxWatch,
|
|
||||||
#[strum(props(default = "1"))]
|
|
||||||
MvboxMove,
|
|
||||||
#[strum(props(default = "0"))]
|
|
||||||
ShowEmails,
|
|
||||||
SaveMimeHeaders,
|
|
||||||
ConfiguredAddr,
|
|
||||||
ConfiguredMailServer,
|
|
||||||
ConfiguredMailUser,
|
|
||||||
ConfiguredMailPw,
|
|
||||||
ConfiguredMailPort,
|
|
||||||
ConfiguredMailSecurity,
|
|
||||||
ConfiguredSendServer,
|
|
||||||
ConfiguredSendUser,
|
|
||||||
ConfiguredSendPw,
|
|
||||||
ConfiguredSendPort,
|
|
||||||
ConfiguredServerFlags,
|
|
||||||
ConfiguredSendSecurity,
|
|
||||||
ConfiguredE2EEEnabled,
|
|
||||||
Configured,
|
|
||||||
// Deprecated
|
|
||||||
#[strum(serialize = "sys.version")]
|
|
||||||
SysVersion,
|
|
||||||
#[strum(serialize = "sys.msgsize_max_recommended")]
|
|
||||||
SysMsgsizeMaxRecommended,
|
|
||||||
#[strum(serialize = "sys.config_keys")]
|
|
||||||
SysConfigKeys,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Context {
|
|
||||||
/// Get a configuration key. Returns `None` if no value is set, and no default value found.
|
|
||||||
pub fn get_config(&self, key: Config) -> Option<String> {
|
|
||||||
let value = match key {
|
|
||||||
Config::Selfavatar => {
|
|
||||||
let rel_path = self.sql.get_config(self, key);
|
|
||||||
rel_path.map(|p| dc_get_abs_path(self, &p).to_str().unwrap().to_string())
|
|
||||||
}
|
|
||||||
Config::SysVersion => Some((&*DC_VERSION_STR).clone()),
|
|
||||||
Config::SysMsgsizeMaxRecommended => Some(format!("{}", 24 * 1024 * 1024 / 4 * 3)),
|
|
||||||
Config::SysConfigKeys => Some(get_config_keys_string()),
|
|
||||||
_ => self.sql.get_config(self, key),
|
|
||||||
};
|
|
||||||
|
|
||||||
if value.is_some() {
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Default values
|
|
||||||
match key {
|
|
||||||
Config::Selfstatus => Some(self.stock_str(StockMessage::StatusLine).into_owned()),
|
|
||||||
_ => key.get_str("default").map(|s| s.to_string()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set the given config key.
|
|
||||||
/// If `None` is passed as a value the value is cleared and set to the default if there is one.
|
|
||||||
pub fn set_config(&self, key: Config, value: Option<&str>) -> Result<(), Error> {
|
|
||||||
match key {
|
|
||||||
Config::Selfavatar if value.is_some() => {
|
|
||||||
let rel_path = std::fs::canonicalize(value.unwrap())?;
|
|
||||||
self.sql
|
|
||||||
.set_config(self, key, Some(&rel_path.to_string_lossy()))
|
|
||||||
}
|
|
||||||
Config::InboxWatch => {
|
|
||||||
let ret = self.sql.set_config(self, key, value);
|
|
||||||
interrupt_imap_idle(self);
|
|
||||||
ret
|
|
||||||
}
|
|
||||||
Config::SentboxWatch => {
|
|
||||||
let ret = self.sql.set_config(self, key, value);
|
|
||||||
interrupt_sentbox_idle(self);
|
|
||||||
ret
|
|
||||||
}
|
|
||||||
Config::MvboxWatch => {
|
|
||||||
let ret = self.sql.set_config(self, key, value);
|
|
||||||
interrupt_mvbox_idle(self);
|
|
||||||
ret
|
|
||||||
}
|
|
||||||
Config::Selfstatus => {
|
|
||||||
let def = self.stock_str(StockMessage::StatusLine);
|
|
||||||
let val = if value.is_none() || value.unwrap() == def {
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
value
|
|
||||||
};
|
|
||||||
|
|
||||||
self.sql.set_config(self, key, val)
|
|
||||||
}
|
|
||||||
_ => self.sql.set_config(self, key, value),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns all available configuration keys concated together.
|
|
||||||
fn get_config_keys_string() -> String {
|
|
||||||
let keys = Config::iter().fold(String::new(), |mut acc, key| {
|
|
||||||
acc += key.as_ref();
|
|
||||||
acc += " ";
|
|
||||||
acc
|
|
||||||
});
|
|
||||||
|
|
||||||
format!(" {} ", keys)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
use std::str::FromStr;
|
|
||||||
use std::string::ToString;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_to_string() {
|
|
||||||
assert_eq!(Config::MailServer.to_string(), "mail_server");
|
|
||||||
assert_eq!(Config::from_str("mail_server"), Ok(Config::MailServer));
|
|
||||||
|
|
||||||
assert_eq!(Config::SysConfigKeys.to_string(), "sys.config_keys");
|
|
||||||
assert_eq!(
|
|
||||||
Config::from_str("sys.config_keys"),
|
|
||||||
Ok(Config::SysConfigKeys)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_default_prop() {
|
|
||||||
assert_eq!(Config::ImapFolder.get_str("default"), Some("INBOX"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,213 +0,0 @@
|
|||||||
use quick_xml;
|
|
||||||
use quick_xml::events::{BytesEnd, BytesStart, BytesText};
|
|
||||||
|
|
||||||
use crate::constants::*;
|
|
||||||
use crate::context::Context;
|
|
||||||
use crate::dc_tools::*;
|
|
||||||
use crate::login_param::LoginParam;
|
|
||||||
use crate::x::*;
|
|
||||||
|
|
||||||
use super::read_autoconf_file;
|
|
||||||
/* ******************************************************************************
|
|
||||||
* Thunderbird's Autoconfigure
|
|
||||||
******************************************************************************/
|
|
||||||
/* documentation: https://developer.mozilla.org/en-US/docs/Mozilla/Thunderbird/Autoconfiguration */
|
|
||||||
#[repr(C)]
|
|
||||||
struct moz_autoconfigure_t<'a> {
|
|
||||||
pub in_0: &'a LoginParam,
|
|
||||||
pub in_emaildomain: &'a str,
|
|
||||||
pub in_emaillocalpart: &'a str,
|
|
||||||
pub out: LoginParam,
|
|
||||||
pub out_imap_set: libc::c_int,
|
|
||||||
pub out_smtp_set: libc::c_int,
|
|
||||||
pub tag_server: libc::c_int,
|
|
||||||
pub tag_config: libc::c_int,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub unsafe fn moz_autoconfigure(
|
|
||||||
context: &Context,
|
|
||||||
url: &str,
|
|
||||||
param_in: &LoginParam,
|
|
||||||
) -> Option<LoginParam> {
|
|
||||||
let xml_raw = read_autoconf_file(context, url);
|
|
||||||
if xml_raw.is_null() {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Split address into local part and domain part.
|
|
||||||
let p = param_in.addr.find("@");
|
|
||||||
if p.is_none() {
|
|
||||||
free(xml_raw as *mut libc::c_void);
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
let (in_emaillocalpart, in_emaildomain) = param_in.addr.split_at(p.unwrap());
|
|
||||||
let in_emaildomain = &in_emaildomain[1..];
|
|
||||||
|
|
||||||
let mut reader = quick_xml::Reader::from_str(as_str(xml_raw));
|
|
||||||
reader.trim_text(true);
|
|
||||||
|
|
||||||
let mut buf = Vec::new();
|
|
||||||
|
|
||||||
let mut moz_ac = moz_autoconfigure_t {
|
|
||||||
in_0: param_in,
|
|
||||||
in_emaildomain,
|
|
||||||
in_emaillocalpart,
|
|
||||||
out: LoginParam::new(),
|
|
||||||
out_imap_set: 0,
|
|
||||||
out_smtp_set: 0,
|
|
||||||
tag_server: 0,
|
|
||||||
tag_config: 0,
|
|
||||||
};
|
|
||||||
loop {
|
|
||||||
match reader.read_event(&mut buf) {
|
|
||||||
Ok(quick_xml::events::Event::Start(ref e)) => {
|
|
||||||
moz_autoconfigure_starttag_cb(e, &mut moz_ac, &reader)
|
|
||||||
}
|
|
||||||
Ok(quick_xml::events::Event::End(ref e)) => moz_autoconfigure_endtag_cb(e, &mut moz_ac),
|
|
||||||
Ok(quick_xml::events::Event::Text(ref e)) => {
|
|
||||||
moz_autoconfigure_text_cb(e, &mut moz_ac, &reader)
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
error!(
|
|
||||||
context,
|
|
||||||
"Configure xml: Error at position {}: {:?}",
|
|
||||||
reader.buffer_position(),
|
|
||||||
e
|
|
||||||
);
|
|
||||||
}
|
|
||||||
Ok(quick_xml::events::Event::Eof) => break,
|
|
||||||
_ => (),
|
|
||||||
}
|
|
||||||
buf.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
if moz_ac.out.mail_server.is_empty()
|
|
||||||
|| moz_ac.out.mail_port == 0
|
|
||||||
|| moz_ac.out.send_server.is_empty()
|
|
||||||
|| moz_ac.out.send_port == 0
|
|
||||||
{
|
|
||||||
let r = moz_ac.out.to_string();
|
|
||||||
warn!(context, "Bad or incomplete autoconfig: {}", r,);
|
|
||||||
free(xml_raw as *mut libc::c_void);
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
free(xml_raw as *mut libc::c_void);
|
|
||||||
Some(moz_ac.out)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn moz_autoconfigure_text_cb<B: std::io::BufRead>(
|
|
||||||
event: &BytesText,
|
|
||||||
moz_ac: &mut moz_autoconfigure_t,
|
|
||||||
reader: &quick_xml::Reader<B>,
|
|
||||||
) {
|
|
||||||
let val = event.unescape_and_decode(reader).unwrap_or_default();
|
|
||||||
|
|
||||||
let addr = &moz_ac.in_0.addr;
|
|
||||||
let email_local = moz_ac.in_emaillocalpart;
|
|
||||||
let email_domain = moz_ac.in_emaildomain;
|
|
||||||
|
|
||||||
let val = val
|
|
||||||
.trim()
|
|
||||||
.replace("%EMAILADDRESS%", addr)
|
|
||||||
.replace("%EMAILLOCALPART%", email_local)
|
|
||||||
.replace("%EMAILDOMAIN%", email_domain);
|
|
||||||
|
|
||||||
if moz_ac.tag_server == 1 {
|
|
||||||
match moz_ac.tag_config {
|
|
||||||
10 => moz_ac.out.mail_server = val,
|
|
||||||
11 => moz_ac.out.mail_port = val.parse().unwrap_or_default(),
|
|
||||||
12 => moz_ac.out.mail_user = val,
|
|
||||||
13 => {
|
|
||||||
let val_lower = val.to_lowercase();
|
|
||||||
if val_lower == "ssl" {
|
|
||||||
moz_ac.out.server_flags |= DC_LP_IMAP_SOCKET_SSL as i32
|
|
||||||
}
|
|
||||||
if val_lower == "starttls" {
|
|
||||||
moz_ac.out.server_flags |= DC_LP_IMAP_SOCKET_STARTTLS as i32
|
|
||||||
}
|
|
||||||
if val_lower == "plain" {
|
|
||||||
moz_ac.out.server_flags |= DC_LP_IMAP_SOCKET_PLAIN as i32
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
} else if moz_ac.tag_server == 2 {
|
|
||||||
match moz_ac.tag_config {
|
|
||||||
10 => moz_ac.out.send_server = val,
|
|
||||||
11 => moz_ac.out.send_port = val.parse().unwrap_or_default(),
|
|
||||||
12 => moz_ac.out.send_user = val,
|
|
||||||
13 => {
|
|
||||||
let val_lower = val.to_lowercase();
|
|
||||||
if val_lower == "ssl" {
|
|
||||||
moz_ac.out.server_flags |= DC_LP_SMTP_SOCKET_SSL as i32
|
|
||||||
}
|
|
||||||
if val_lower == "starttls" {
|
|
||||||
moz_ac.out.server_flags |= DC_LP_SMTP_SOCKET_STARTTLS as i32
|
|
||||||
}
|
|
||||||
if val_lower == "plain" {
|
|
||||||
moz_ac.out.server_flags |= DC_LP_SMTP_SOCKET_PLAIN as i32
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn moz_autoconfigure_endtag_cb(event: &BytesEnd, moz_ac: &mut moz_autoconfigure_t) {
|
|
||||||
let tag = String::from_utf8_lossy(event.name()).trim().to_lowercase();
|
|
||||||
|
|
||||||
if tag == "incomingserver" {
|
|
||||||
moz_ac.tag_server = 0;
|
|
||||||
moz_ac.tag_config = 0;
|
|
||||||
moz_ac.out_imap_set = 1;
|
|
||||||
} else if tag == "outgoingserver" {
|
|
||||||
moz_ac.tag_server = 0;
|
|
||||||
moz_ac.tag_config = 0;
|
|
||||||
moz_ac.out_smtp_set = 1;
|
|
||||||
} else {
|
|
||||||
moz_ac.tag_config = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn moz_autoconfigure_starttag_cb<B: std::io::BufRead>(
|
|
||||||
event: &BytesStart,
|
|
||||||
moz_ac: &mut moz_autoconfigure_t,
|
|
||||||
reader: &quick_xml::Reader<B>,
|
|
||||||
) {
|
|
||||||
let tag = String::from_utf8_lossy(event.name()).trim().to_lowercase();
|
|
||||||
|
|
||||||
if tag == "incomingserver" {
|
|
||||||
moz_ac.tag_server = if let Some(typ) = event.attributes().find(|attr| {
|
|
||||||
attr.as_ref()
|
|
||||||
.map(|a| String::from_utf8_lossy(a.key).trim().to_lowercase() == "type")
|
|
||||||
.unwrap_or_default()
|
|
||||||
}) {
|
|
||||||
let typ = typ
|
|
||||||
.unwrap()
|
|
||||||
.unescape_and_decode_value(reader)
|
|
||||||
.unwrap_or_default()
|
|
||||||
.to_lowercase();
|
|
||||||
|
|
||||||
if typ == "imap" && moz_ac.out_imap_set == 0 {
|
|
||||||
1
|
|
||||||
} else {
|
|
||||||
0
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
0
|
|
||||||
};
|
|
||||||
moz_ac.tag_config = 0;
|
|
||||||
} else if tag == "outgoingserver" {
|
|
||||||
moz_ac.tag_server = if moz_ac.out_smtp_set == 0 { 2 } else { 0 };
|
|
||||||
moz_ac.tag_config = 0;
|
|
||||||
} else if tag == "hostname" {
|
|
||||||
moz_ac.tag_config = 10;
|
|
||||||
} else if tag == "port" {
|
|
||||||
moz_ac.tag_config = 11;
|
|
||||||
} else if tag == "sockettype" {
|
|
||||||
moz_ac.tag_config = 13;
|
|
||||||
} else if tag == "username" {
|
|
||||||
moz_ac.tag_config = 12;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,213 +0,0 @@
|
|||||||
use quick_xml;
|
|
||||||
use quick_xml::events::{BytesEnd, BytesStart, BytesText};
|
|
||||||
|
|
||||||
use crate::constants::*;
|
|
||||||
use crate::context::Context;
|
|
||||||
use crate::dc_tools::*;
|
|
||||||
use crate::login_param::LoginParam;
|
|
||||||
use crate::x::*;
|
|
||||||
use std::ptr;
|
|
||||||
|
|
||||||
use super::read_autoconf_file;
|
|
||||||
/* ******************************************************************************
|
|
||||||
* Outlook's Autodiscover
|
|
||||||
******************************************************************************/
|
|
||||||
#[repr(C)]
|
|
||||||
struct outlk_autodiscover_t<'a> {
|
|
||||||
pub in_0: &'a LoginParam,
|
|
||||||
pub out: LoginParam,
|
|
||||||
pub out_imap_set: libc::c_int,
|
|
||||||
pub out_smtp_set: libc::c_int,
|
|
||||||
pub tag_config: libc::c_int,
|
|
||||||
pub config: [*mut libc::c_char; 6],
|
|
||||||
pub redirect: *mut libc::c_char,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub unsafe fn outlk_autodiscover(
|
|
||||||
context: &Context,
|
|
||||||
url__: &str,
|
|
||||||
param_in: &LoginParam,
|
|
||||||
) -> Option<LoginParam> {
|
|
||||||
let mut xml_raw: *mut libc::c_char = ptr::null_mut();
|
|
||||||
let mut url = url__.strdup();
|
|
||||||
let mut outlk_ad = outlk_autodiscover_t {
|
|
||||||
in_0: param_in,
|
|
||||||
out: LoginParam::new(),
|
|
||||||
out_imap_set: 0,
|
|
||||||
out_smtp_set: 0,
|
|
||||||
tag_config: 0,
|
|
||||||
config: [ptr::null_mut(); 6],
|
|
||||||
redirect: ptr::null_mut(),
|
|
||||||
};
|
|
||||||
let ok_to_continue;
|
|
||||||
let mut i = 0;
|
|
||||||
loop {
|
|
||||||
if !(i < 10) {
|
|
||||||
ok_to_continue = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
memset(
|
|
||||||
&mut outlk_ad as *mut outlk_autodiscover_t as *mut libc::c_void,
|
|
||||||
0,
|
|
||||||
::std::mem::size_of::<outlk_autodiscover_t>(),
|
|
||||||
);
|
|
||||||
xml_raw = read_autoconf_file(context, as_str(url));
|
|
||||||
if xml_raw.is_null() {
|
|
||||||
ok_to_continue = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut reader = quick_xml::Reader::from_str(as_str(xml_raw));
|
|
||||||
reader.trim_text(true);
|
|
||||||
|
|
||||||
let mut buf = Vec::new();
|
|
||||||
|
|
||||||
loop {
|
|
||||||
match reader.read_event(&mut buf) {
|
|
||||||
Ok(quick_xml::events::Event::Start(ref e)) => {
|
|
||||||
outlk_autodiscover_starttag_cb(e, &mut outlk_ad)
|
|
||||||
}
|
|
||||||
Ok(quick_xml::events::Event::End(ref e)) => {
|
|
||||||
outlk_autodiscover_endtag_cb(e, &mut outlk_ad)
|
|
||||||
}
|
|
||||||
Ok(quick_xml::events::Event::Text(ref e)) => {
|
|
||||||
outlk_autodiscover_text_cb(e, &mut outlk_ad, &reader)
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
error!(
|
|
||||||
context,
|
|
||||||
"Configure xml: Error at position {}: {:?}",
|
|
||||||
reader.buffer_position(),
|
|
||||||
e
|
|
||||||
);
|
|
||||||
}
|
|
||||||
Ok(quick_xml::events::Event::Eof) => break,
|
|
||||||
_ => (),
|
|
||||||
}
|
|
||||||
buf.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
if !(!outlk_ad.config[5].is_null()
|
|
||||||
&& 0 != *outlk_ad.config[5usize].offset(0isize) as libc::c_int)
|
|
||||||
{
|
|
||||||
ok_to_continue = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
free(url as *mut libc::c_void);
|
|
||||||
url = dc_strdup(outlk_ad.config[5usize]);
|
|
||||||
|
|
||||||
outlk_clean_config(&mut outlk_ad);
|
|
||||||
free(xml_raw as *mut libc::c_void);
|
|
||||||
xml_raw = ptr::null_mut();
|
|
||||||
i += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ok_to_continue {
|
|
||||||
if outlk_ad.out.mail_server.is_empty()
|
|
||||||
|| outlk_ad.out.mail_port == 0
|
|
||||||
|| outlk_ad.out.send_server.is_empty()
|
|
||||||
|| outlk_ad.out.send_port == 0
|
|
||||||
{
|
|
||||||
let r = outlk_ad.out.to_string();
|
|
||||||
warn!(context, "Bad or incomplete autoconfig: {}", r,);
|
|
||||||
free(url as *mut libc::c_void);
|
|
||||||
free(xml_raw as *mut libc::c_void);
|
|
||||||
outlk_clean_config(&mut outlk_ad);
|
|
||||||
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
free(url as *mut libc::c_void);
|
|
||||||
free(xml_raw as *mut libc::c_void);
|
|
||||||
outlk_clean_config(&mut outlk_ad);
|
|
||||||
Some(outlk_ad.out)
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe fn outlk_clean_config(mut outlk_ad: *mut outlk_autodiscover_t) {
|
|
||||||
for i in 0..6 {
|
|
||||||
free((*outlk_ad).config[i] as *mut libc::c_void);
|
|
||||||
(*outlk_ad).config[i] = ptr::null_mut();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn outlk_autodiscover_text_cb<B: std::io::BufRead>(
|
|
||||||
event: &BytesText,
|
|
||||||
outlk_ad: &mut outlk_autodiscover_t,
|
|
||||||
reader: &quick_xml::Reader<B>,
|
|
||||||
) {
|
|
||||||
let val = event.unescape_and_decode(reader).unwrap_or_default();
|
|
||||||
|
|
||||||
unsafe {
|
|
||||||
free(outlk_ad.config[outlk_ad.tag_config as usize].cast());
|
|
||||||
outlk_ad.config[outlk_ad.tag_config as usize] = val.trim().strdup();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe fn outlk_autodiscover_endtag_cb(event: &BytesEnd, outlk_ad: &mut outlk_autodiscover_t) {
|
|
||||||
let tag = String::from_utf8_lossy(event.name()).trim().to_lowercase();
|
|
||||||
|
|
||||||
if tag == "protocol" {
|
|
||||||
if !outlk_ad.config[1].is_null() {
|
|
||||||
let port = dc_atoi_null_is_0(outlk_ad.config[3]);
|
|
||||||
let ssl_on = (!outlk_ad.config[4].is_null()
|
|
||||||
&& strcasecmp(
|
|
||||||
outlk_ad.config[4],
|
|
||||||
b"on\x00" as *const u8 as *const libc::c_char,
|
|
||||||
) == 0) as libc::c_int;
|
|
||||||
let ssl_off = (!outlk_ad.config[4].is_null()
|
|
||||||
&& strcasecmp(
|
|
||||||
outlk_ad.config[4],
|
|
||||||
b"off\x00" as *const u8 as *const libc::c_char,
|
|
||||||
) == 0) as libc::c_int;
|
|
||||||
if strcasecmp(
|
|
||||||
outlk_ad.config[1],
|
|
||||||
b"imap\x00" as *const u8 as *const libc::c_char,
|
|
||||||
) == 0
|
|
||||||
&& outlk_ad.out_imap_set == 0
|
|
||||||
{
|
|
||||||
outlk_ad.out.mail_server = to_string(outlk_ad.config[2]);
|
|
||||||
outlk_ad.out.mail_port = port;
|
|
||||||
if 0 != ssl_on {
|
|
||||||
outlk_ad.out.server_flags |= DC_LP_IMAP_SOCKET_SSL as i32
|
|
||||||
} else if 0 != ssl_off {
|
|
||||||
outlk_ad.out.server_flags |= DC_LP_IMAP_SOCKET_PLAIN as i32
|
|
||||||
}
|
|
||||||
outlk_ad.out_imap_set = 1
|
|
||||||
} else if strcasecmp(
|
|
||||||
outlk_ad.config[1usize],
|
|
||||||
b"smtp\x00" as *const u8 as *const libc::c_char,
|
|
||||||
) == 0
|
|
||||||
&& outlk_ad.out_smtp_set == 0
|
|
||||||
{
|
|
||||||
outlk_ad.out.send_server = to_string(outlk_ad.config[2]);
|
|
||||||
outlk_ad.out.send_port = port;
|
|
||||||
if 0 != ssl_on {
|
|
||||||
outlk_ad.out.server_flags |= DC_LP_SMTP_SOCKET_SSL as i32
|
|
||||||
} else if 0 != ssl_off {
|
|
||||||
outlk_ad.out.server_flags |= DC_LP_SMTP_SOCKET_PLAIN as i32
|
|
||||||
}
|
|
||||||
outlk_ad.out_smtp_set = 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
outlk_clean_config(outlk_ad);
|
|
||||||
}
|
|
||||||
outlk_ad.tag_config = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn outlk_autodiscover_starttag_cb(event: &BytesStart, outlk_ad: &mut outlk_autodiscover_t) {
|
|
||||||
let tag = String::from_utf8_lossy(event.name()).trim().to_lowercase();
|
|
||||||
|
|
||||||
if tag == "protocol" {
|
|
||||||
unsafe { outlk_clean_config(outlk_ad) };
|
|
||||||
} else if tag == "type" {
|
|
||||||
outlk_ad.tag_config = 1
|
|
||||||
} else if tag == "server" {
|
|
||||||
outlk_ad.tag_config = 2
|
|
||||||
} else if tag == "port" {
|
|
||||||
outlk_ad.tag_config = 3
|
|
||||||
} else if tag == "ssl" {
|
|
||||||
outlk_ad.tag_config = 4
|
|
||||||
} else if tag == "redirecturl" {
|
|
||||||
outlk_ad.tag_config = 5
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,662 +0,0 @@
|
|||||||
use percent_encoding::{utf8_percent_encode, NON_ALPHANUMERIC};
|
|
||||||
|
|
||||||
use crate::constants::*;
|
|
||||||
use crate::context::Context;
|
|
||||||
use crate::dc_tools::*;
|
|
||||||
use crate::e2ee;
|
|
||||||
use crate::imap::*;
|
|
||||||
use crate::job::*;
|
|
||||||
use crate::login_param::LoginParam;
|
|
||||||
use crate::oauth2::*;
|
|
||||||
use crate::param::Params;
|
|
||||||
|
|
||||||
mod auto_outlook;
|
|
||||||
use auto_outlook::outlk_autodiscover;
|
|
||||||
mod auto_mozilla;
|
|
||||||
use auto_mozilla::moz_autoconfigure;
|
|
||||||
|
|
||||||
macro_rules! progress {
|
|
||||||
($context:tt, $progress:expr) => {
|
|
||||||
assert!(
|
|
||||||
$progress > 0 && $progress <= 1000,
|
|
||||||
"value in range 0..1000 expected with: 0=error, 1..999=progress, 1000=success"
|
|
||||||
);
|
|
||||||
$context.call_cb($crate::events::Event::ConfigureProgress($progress));
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// connect
|
|
||||||
pub unsafe fn configure(context: &Context) {
|
|
||||||
if dc_has_ongoing(context) {
|
|
||||||
warn!(context, "There is already another ongoing process running.",);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
job_kill_action(context, Action::ConfigureImap);
|
|
||||||
job_add(context, Action::ConfigureImap, 0, Params::new(), 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Check if the context is already configured.
|
|
||||||
pub fn dc_is_configured(context: &Context) -> bool {
|
|
||||||
context.sql.get_config_bool(context, "configured")
|
|
||||||
}
|
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
* Configure JOB
|
|
||||||
******************************************************************************/
|
|
||||||
// the other dc_job_do_DC_JOB_*() functions are declared static in the c-file
|
|
||||||
#[allow(non_snake_case, unused_must_use)]
|
|
||||||
pub unsafe fn dc_job_do_DC_JOB_CONFIGURE_IMAP(context: &Context, _job: &Job) {
|
|
||||||
let mut success = false;
|
|
||||||
let mut imap_connected_here = false;
|
|
||||||
let mut smtp_connected_here = false;
|
|
||||||
let mut ongoing_allocated_here = false;
|
|
||||||
|
|
||||||
let mut param_autoconfig: Option<LoginParam> = None;
|
|
||||||
if dc_alloc_ongoing(context) {
|
|
||||||
ongoing_allocated_here = true;
|
|
||||||
if !context.sql.is_open() {
|
|
||||||
error!(context, "Cannot configure, database not opened.",);
|
|
||||||
} else {
|
|
||||||
context.inbox.read().unwrap().disconnect(context);
|
|
||||||
context
|
|
||||||
.sentbox_thread
|
|
||||||
.read()
|
|
||||||
.unwrap()
|
|
||||||
.imap
|
|
||||||
.disconnect(context);
|
|
||||||
context
|
|
||||||
.mvbox_thread
|
|
||||||
.read()
|
|
||||||
.unwrap()
|
|
||||||
.imap
|
|
||||||
.disconnect(context);
|
|
||||||
context.smtp.clone().lock().unwrap().disconnect();
|
|
||||||
info!(context, "Configure ...",);
|
|
||||||
|
|
||||||
let s_a = context.running_state.clone();
|
|
||||||
let s = s_a.read().unwrap();
|
|
||||||
|
|
||||||
// Variables that are shared between steps:
|
|
||||||
let mut param = LoginParam::from_database(context, "");
|
|
||||||
// need all vars here to be mutable because rust thinks the same step could be called multiple times
|
|
||||||
// and also initialize, because otherwise rust thinks it's used while unitilized, even if thats not the case as the loop goes only forward
|
|
||||||
let mut param_domain = "undefined.undefined".to_owned();
|
|
||||||
let mut param_addr_urlencoded: String =
|
|
||||||
"Internal Error: this value should never be used".to_owned();
|
|
||||||
let mut keep_flags = std::i32::MAX;
|
|
||||||
|
|
||||||
const STEP_3_INDEX: u8 = 13;
|
|
||||||
let mut step_counter: u8 = 0;
|
|
||||||
while !s.shall_stop_ongoing {
|
|
||||||
step_counter = step_counter + 1;
|
|
||||||
|
|
||||||
let success = match step_counter {
|
|
||||||
// Read login parameters from the database
|
|
||||||
1 => {
|
|
||||||
progress!(context, 1);
|
|
||||||
if param.addr.is_empty() {
|
|
||||||
error!(context, "Please enter an email address.",);
|
|
||||||
}
|
|
||||||
!param.addr.is_empty()
|
|
||||||
}
|
|
||||||
// Step 1: Load the parameters and check email-address and password
|
|
||||||
2 => {
|
|
||||||
if 0 != param.server_flags & 0x2 {
|
|
||||||
// the used oauth2 addr may differ, check this.
|
|
||||||
// if dc_get_oauth2_addr() is not available in the oauth2 implementation,
|
|
||||||
// just use the given one.
|
|
||||||
progress!(context, 10);
|
|
||||||
if let Some(oauth2_addr) =
|
|
||||||
dc_get_oauth2_addr(context, ¶m.addr, ¶m.mail_pw)
|
|
||||||
.and_then(|e| e.parse().ok())
|
|
||||||
{
|
|
||||||
param.addr = oauth2_addr;
|
|
||||||
context
|
|
||||||
.sql
|
|
||||||
.set_config(context, "addr", Some(param.addr.as_str()))
|
|
||||||
.ok();
|
|
||||||
}
|
|
||||||
progress!(context, 20);
|
|
||||||
}
|
|
||||||
true // no oauth? - just continue it's no error
|
|
||||||
}
|
|
||||||
3 => {
|
|
||||||
if let Ok(parsed) = param.addr.parse() {
|
|
||||||
let parsed: EmailAddress = parsed;
|
|
||||||
param_domain = parsed.domain;
|
|
||||||
param_addr_urlencoded =
|
|
||||||
utf8_percent_encode(¶m.addr, NON_ALPHANUMERIC).to_string();
|
|
||||||
true
|
|
||||||
} else {
|
|
||||||
error!(context, "Bad email-address.");
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Step 2: Autoconfig
|
|
||||||
4 => {
|
|
||||||
progress!(context, 200);
|
|
||||||
if param.mail_server.is_empty()
|
|
||||||
&& param.mail_port == 0
|
|
||||||
/*&¶m.mail_user.is_empty() -- the user can enter a loginname which is used by autoconfig then */
|
|
||||||
&& param.send_server.is_empty()
|
|
||||||
&& param.send_port == 0
|
|
||||||
&& param.send_user.is_empty()
|
|
||||||
/*&¶m.send_pw.is_empty() -- the password cannot be auto-configured and is no criterion for autoconfig or not */
|
|
||||||
&& param.server_flags & !0x2 == 0
|
|
||||||
{
|
|
||||||
keep_flags = param.server_flags & 0x2;
|
|
||||||
} else {
|
|
||||||
// Autoconfig is not needed so skip it.
|
|
||||||
step_counter = STEP_3_INDEX - 1;
|
|
||||||
}
|
|
||||||
true
|
|
||||||
}
|
|
||||||
/* A. Search configurations from the domain used in the email-address, prefer encrypted */
|
|
||||||
5 => {
|
|
||||||
if param_autoconfig.is_none() {
|
|
||||||
let url = format!(
|
|
||||||
"https://autoconfig.{}/mail/config-v1.1.xml?emailaddress={}",
|
|
||||||
param_domain, param_addr_urlencoded
|
|
||||||
);
|
|
||||||
param_autoconfig = moz_autoconfigure(context, &url, ¶m);
|
|
||||||
}
|
|
||||||
true
|
|
||||||
}
|
|
||||||
6 => {
|
|
||||||
progress!(context, 300);
|
|
||||||
if param_autoconfig.is_none() {
|
|
||||||
// the doc does not mention `emailaddress=`, however, Thunderbird adds it, see https://releases.mozilla.org/pub/thunderbird/ , which makes some sense
|
|
||||||
let url = format!(
|
|
||||||
"https://{}/.well-known/autoconfig/mail/config-v1.1.xml?emailaddress={}",
|
|
||||||
param_domain,
|
|
||||||
param_addr_urlencoded
|
|
||||||
);
|
|
||||||
param_autoconfig = moz_autoconfigure(context, &url, ¶m);
|
|
||||||
}
|
|
||||||
true
|
|
||||||
}
|
|
||||||
/* Outlook section start ------------- */
|
|
||||||
/* Outlook uses always SSL but different domains (this comment describes the next two steps) */
|
|
||||||
7 => {
|
|
||||||
progress!(context, 310);
|
|
||||||
if param_autoconfig.is_none() {
|
|
||||||
let url = format!(
|
|
||||||
"https://{}{}/autodiscover/autodiscover.xml",
|
|
||||||
"", param_domain
|
|
||||||
);
|
|
||||||
param_autoconfig = outlk_autodiscover(context, &url, ¶m);
|
|
||||||
}
|
|
||||||
true
|
|
||||||
}
|
|
||||||
8 => {
|
|
||||||
progress!(context, 320);
|
|
||||||
if param_autoconfig.is_none() {
|
|
||||||
let url = format!(
|
|
||||||
"https://{}{}/autodiscover/autodiscover.xml",
|
|
||||||
"autodiscover.", param_domain
|
|
||||||
);
|
|
||||||
param_autoconfig = outlk_autodiscover(context, &url, ¶m);
|
|
||||||
}
|
|
||||||
true
|
|
||||||
}
|
|
||||||
/* ----------- Outlook section end */
|
|
||||||
9 => {
|
|
||||||
progress!(context, 330);
|
|
||||||
if param_autoconfig.is_none() {
|
|
||||||
let url = format!(
|
|
||||||
"http://autoconfig.{}/mail/config-v1.1.xml?emailaddress={}",
|
|
||||||
param_domain, param_addr_urlencoded
|
|
||||||
);
|
|
||||||
param_autoconfig = moz_autoconfigure(context, &url, ¶m);
|
|
||||||
}
|
|
||||||
true
|
|
||||||
}
|
|
||||||
10 => {
|
|
||||||
progress!(context, 340);
|
|
||||||
if param_autoconfig.is_none() {
|
|
||||||
// do not transfer the email-address unencrypted
|
|
||||||
let url = format!(
|
|
||||||
"http://{}/.well-known/autoconfig/mail/config-v1.1.xml",
|
|
||||||
param_domain
|
|
||||||
);
|
|
||||||
param_autoconfig = moz_autoconfigure(context, &url, ¶m);
|
|
||||||
}
|
|
||||||
true
|
|
||||||
}
|
|
||||||
/* B. If we have no configuration yet, search configuration in Thunderbird's centeral database */
|
|
||||||
11 => {
|
|
||||||
progress!(context, 350);
|
|
||||||
if param_autoconfig.is_none() {
|
|
||||||
/* always SSL for Thunderbird's database */
|
|
||||||
let url =
|
|
||||||
format!("https://autoconfig.thunderbird.net/v1.1/{}", param_domain);
|
|
||||||
param_autoconfig = moz_autoconfigure(context, &url, ¶m);
|
|
||||||
}
|
|
||||||
true
|
|
||||||
}
|
|
||||||
/* C. Do we have any result? */
|
|
||||||
12 => {
|
|
||||||
progress!(context, 500);
|
|
||||||
if let Some(ref cfg) = param_autoconfig {
|
|
||||||
info!(context, "Got autoconfig: {}", &cfg);
|
|
||||||
if !cfg.mail_user.is_empty() {
|
|
||||||
param.mail_user = cfg.mail_user.clone();
|
|
||||||
}
|
|
||||||
param.mail_server = cfg.mail_server.clone(); /* all other values are always NULL when entering autoconfig */
|
|
||||||
param.mail_port = cfg.mail_port;
|
|
||||||
param.send_server = cfg.send_server.clone();
|
|
||||||
param.send_port = cfg.send_port;
|
|
||||||
param.send_user = cfg.send_user.clone();
|
|
||||||
param.server_flags = cfg.server_flags;
|
|
||||||
/* although param_autoconfig's data are no longer needed from, it is important to keep the object as
|
|
||||||
we may enter "deep guessing" if we could not read a configuration */
|
|
||||||
}
|
|
||||||
param.server_flags |= keep_flags;
|
|
||||||
true
|
|
||||||
}
|
|
||||||
// Step 3: Fill missing fields with defaults
|
|
||||||
13 => {
|
|
||||||
// if you move this, don't forget to update STEP_3_INDEX, too
|
|
||||||
if param.mail_server.is_empty() {
|
|
||||||
param.mail_server = format!("imap.{}", param_domain,)
|
|
||||||
}
|
|
||||||
if param.mail_port == 0 {
|
|
||||||
param.mail_port = if 0 != param.server_flags & (0x100 | 0x400) {
|
|
||||||
143
|
|
||||||
} else {
|
|
||||||
993
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if param.mail_user.is_empty() {
|
|
||||||
param.mail_user = param.addr.clone();
|
|
||||||
}
|
|
||||||
if param.send_server.is_empty() && !param.mail_server.is_empty() {
|
|
||||||
param.send_server = param.mail_server.clone();
|
|
||||||
if param.send_server.starts_with("imap.") {
|
|
||||||
param.send_server = param.send_server.replacen("imap", "smtp", 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if param.send_port == 0 {
|
|
||||||
param.send_port =
|
|
||||||
if 0 != param.server_flags & DC_LP_SMTP_SOCKET_STARTTLS as i32 {
|
|
||||||
587
|
|
||||||
} else if 0 != param.server_flags & DC_LP_SMTP_SOCKET_PLAIN as i32 {
|
|
||||||
25
|
|
||||||
} else {
|
|
||||||
465
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if param.send_user.is_empty() && !param.mail_user.is_empty() {
|
|
||||||
param.send_user = param.mail_user.clone();
|
|
||||||
}
|
|
||||||
if param.send_pw.is_empty() && !param.mail_pw.is_empty() {
|
|
||||||
param.send_pw = param.mail_pw.clone()
|
|
||||||
}
|
|
||||||
if !dc_exactly_one_bit_set(param.server_flags & DC_LP_AUTH_FLAGS as i32) {
|
|
||||||
param.server_flags &= !(DC_LP_AUTH_FLAGS as i32);
|
|
||||||
param.server_flags |= DC_LP_AUTH_NORMAL as i32
|
|
||||||
}
|
|
||||||
if !dc_exactly_one_bit_set(
|
|
||||||
param.server_flags & DC_LP_IMAP_SOCKET_FLAGS as i32,
|
|
||||||
) {
|
|
||||||
param.server_flags &= !(DC_LP_IMAP_SOCKET_FLAGS as i32);
|
|
||||||
param.server_flags |= if param.send_port == 143 {
|
|
||||||
DC_LP_IMAP_SOCKET_STARTTLS as i32
|
|
||||||
} else {
|
|
||||||
DC_LP_IMAP_SOCKET_SSL as i32
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !dc_exactly_one_bit_set(
|
|
||||||
param.server_flags & (DC_LP_SMTP_SOCKET_FLAGS as i32),
|
|
||||||
) {
|
|
||||||
param.server_flags &= !(DC_LP_SMTP_SOCKET_FLAGS as i32);
|
|
||||||
param.server_flags |= if param.send_port == 587 {
|
|
||||||
DC_LP_SMTP_SOCKET_STARTTLS as i32
|
|
||||||
} else if param.send_port == 25 {
|
|
||||||
DC_LP_SMTP_SOCKET_PLAIN as i32
|
|
||||||
} else {
|
|
||||||
DC_LP_SMTP_SOCKET_SSL as i32
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/* do we have a complete configuration? */
|
|
||||||
if param.mail_server.is_empty()
|
|
||||||
|| param.mail_port == 0
|
|
||||||
|| param.mail_user.is_empty()
|
|
||||||
|| param.mail_pw.is_empty()
|
|
||||||
|| param.send_server.is_empty()
|
|
||||||
|| param.send_port == 0
|
|
||||||
|| param.send_user.is_empty()
|
|
||||||
|| param.send_pw.is_empty()
|
|
||||||
|| param.server_flags == 0
|
|
||||||
{
|
|
||||||
error!(context, "Account settings incomplete.");
|
|
||||||
false
|
|
||||||
} else {
|
|
||||||
true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
14 => {
|
|
||||||
progress!(context, 600);
|
|
||||||
/* try to connect to IMAP - if we did not got an autoconfig,
|
|
||||||
do some further tries with different settings and username variations */
|
|
||||||
let ok_to_continue8;
|
|
||||||
let mut username_variation = 0;
|
|
||||||
loop {
|
|
||||||
if !(username_variation <= 1) {
|
|
||||||
ok_to_continue8 = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
info!(context, "Trying: {}", ¶m);
|
|
||||||
|
|
||||||
if context.inbox.read().unwrap().connect(context, ¶m) {
|
|
||||||
ok_to_continue8 = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if !param_autoconfig.is_none() {
|
|
||||||
ok_to_continue8 = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
// probe STARTTLS/993
|
|
||||||
if s.shall_stop_ongoing {
|
|
||||||
ok_to_continue8 = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
progress!(context, 650 + username_variation * 30);
|
|
||||||
param.server_flags &= !(0x100 | 0x200 | 0x400);
|
|
||||||
param.server_flags |= 0x100;
|
|
||||||
info!(context, "Trying: {}", ¶m);
|
|
||||||
|
|
||||||
if context.inbox.read().unwrap().connect(context, ¶m) {
|
|
||||||
ok_to_continue8 = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
// probe STARTTLS/143
|
|
||||||
if s.shall_stop_ongoing {
|
|
||||||
ok_to_continue8 = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
progress!(context, 660 + username_variation * 30);
|
|
||||||
param.mail_port = 143;
|
|
||||||
info!(context, "Trying: {}", ¶m);
|
|
||||||
|
|
||||||
if context.inbox.read().unwrap().connect(context, ¶m) {
|
|
||||||
ok_to_continue8 = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if 0 != username_variation {
|
|
||||||
ok_to_continue8 = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
// next probe round with only the localpart of the email-address as the loginname
|
|
||||||
if s.shall_stop_ongoing {
|
|
||||||
ok_to_continue8 = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
progress!(context, 670 + username_variation * 30);
|
|
||||||
param.server_flags &= !(0x100 | 0x200 | 0x400);
|
|
||||||
param.server_flags |= 0x200;
|
|
||||||
param.mail_port = 993;
|
|
||||||
|
|
||||||
if let Some(at) = param.mail_user.find('@') {
|
|
||||||
param.mail_user = param.mail_user.split_at(at).0.to_string();
|
|
||||||
}
|
|
||||||
if let Some(at) = param.send_user.find('@') {
|
|
||||||
param.send_user = param.send_user.split_at(at).0.to_string();
|
|
||||||
}
|
|
||||||
|
|
||||||
username_variation += 1
|
|
||||||
}
|
|
||||||
if ok_to_continue8 {
|
|
||||||
// success, so we are connected and should disconnect in cleanup
|
|
||||||
imap_connected_here = true;
|
|
||||||
}
|
|
||||||
ok_to_continue8
|
|
||||||
}
|
|
||||||
15 => {
|
|
||||||
progress!(context, 800);
|
|
||||||
let success;
|
|
||||||
/* try to connect to SMTP - if we did not got an autoconfig, the first try was SSL-465 and we do a second try with STARTTLS-587 */
|
|
||||||
if !context
|
|
||||||
.smtp
|
|
||||||
.clone()
|
|
||||||
.lock()
|
|
||||||
.unwrap()
|
|
||||||
.connect(context, ¶m)
|
|
||||||
{
|
|
||||||
if !param_autoconfig.is_none() {
|
|
||||||
success = false;
|
|
||||||
} else if s.shall_stop_ongoing {
|
|
||||||
success = false;
|
|
||||||
} else {
|
|
||||||
progress!(context, 850);
|
|
||||||
param.server_flags &= !(DC_LP_SMTP_SOCKET_FLAGS as i32);
|
|
||||||
param.server_flags |= DC_LP_SMTP_SOCKET_STARTTLS as i32;
|
|
||||||
param.send_port = 587;
|
|
||||||
info!(context, "Trying: {}", ¶m);
|
|
||||||
|
|
||||||
if !context
|
|
||||||
.smtp
|
|
||||||
.clone()
|
|
||||||
.lock()
|
|
||||||
.unwrap()
|
|
||||||
.connect(context, ¶m)
|
|
||||||
{
|
|
||||||
if s.shall_stop_ongoing {
|
|
||||||
success = false;
|
|
||||||
} else {
|
|
||||||
progress!(context, 860);
|
|
||||||
param.server_flags &= !(DC_LP_SMTP_SOCKET_FLAGS as i32);
|
|
||||||
param.server_flags |= DC_LP_SMTP_SOCKET_STARTTLS as i32;
|
|
||||||
param.send_port = 25;
|
|
||||||
info!(context, "Trying: {}", ¶m);
|
|
||||||
|
|
||||||
if !context
|
|
||||||
.smtp
|
|
||||||
.clone()
|
|
||||||
.lock()
|
|
||||||
.unwrap()
|
|
||||||
.connect(context, ¶m)
|
|
||||||
{
|
|
||||||
success = false;
|
|
||||||
} else {
|
|
||||||
success = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
success = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
success = true;
|
|
||||||
}
|
|
||||||
if success {
|
|
||||||
smtp_connected_here = true;
|
|
||||||
}
|
|
||||||
success
|
|
||||||
}
|
|
||||||
16 => {
|
|
||||||
progress!(context, 900);
|
|
||||||
let flags: libc::c_int = if 0
|
|
||||||
!= context
|
|
||||||
.sql
|
|
||||||
.get_config_int(context, "mvbox_watch")
|
|
||||||
.unwrap_or_else(|| 1)
|
|
||||||
|| 0 != context
|
|
||||||
.sql
|
|
||||||
.get_config_int(context, "mvbox_move")
|
|
||||||
.unwrap_or_else(|| 1)
|
|
||||||
{
|
|
||||||
DC_CREATE_MVBOX as i32
|
|
||||||
} else {
|
|
||||||
0
|
|
||||||
};
|
|
||||||
context
|
|
||||||
.inbox
|
|
||||||
.read()
|
|
||||||
.unwrap()
|
|
||||||
.configure_folders(context, flags);
|
|
||||||
true
|
|
||||||
}
|
|
||||||
17 => {
|
|
||||||
progress!(context, 910);
|
|
||||||
/* configuration success - write back the configured parameters with the "configured_" prefix; also write the "configured"-flag */
|
|
||||||
param
|
|
||||||
.save_to_database(
|
|
||||||
context,
|
|
||||||
"configured_", /*the trailing underscore is correct*/
|
|
||||||
)
|
|
||||||
.ok();
|
|
||||||
|
|
||||||
context.sql.set_config_bool(context, "configured", true);
|
|
||||||
true
|
|
||||||
}
|
|
||||||
18 => {
|
|
||||||
progress!(context, 920);
|
|
||||||
// we generate the keypair just now - we could also postpone this until the first message is sent, however,
|
|
||||||
// this may result in a unexpected and annoying delay when the user sends his very first message
|
|
||||||
// (~30 seconds on a Moto G4 play) and might looks as if message sending is always that slow.
|
|
||||||
e2ee::ensure_secret_key_exists(context);
|
|
||||||
success = true;
|
|
||||||
info!(context, "Configure completed.");
|
|
||||||
progress!(context, 940);
|
|
||||||
break; // We are done here
|
|
||||||
}
|
|
||||||
|
|
||||||
_ => {
|
|
||||||
error!(context, "Internal error: step counter out of bound",);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if !success {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if imap_connected_here {
|
|
||||||
context.inbox.read().unwrap().disconnect(context);
|
|
||||||
}
|
|
||||||
if smtp_connected_here {
|
|
||||||
context.smtp.clone().lock().unwrap().disconnect();
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
if !success {
|
|
||||||
// disconnect if configure did not succeed
|
|
||||||
if imap_connected_here {
|
|
||||||
// context.inbox.read().unwrap().disconnect(context);
|
|
||||||
}
|
|
||||||
if smtp_connected_here {
|
|
||||||
// context.smtp.clone().lock().unwrap().disconnect();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
assert!(imap_connected_here && smtp_connected_here);
|
|
||||||
info!(
|
|
||||||
context,
|
|
||||||
0, "Keeping IMAP/SMTP connections open after successful configuration"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
if ongoing_allocated_here {
|
|
||||||
dc_free_ongoing(context);
|
|
||||||
}
|
|
||||||
|
|
||||||
progress!(context, if success { 1000 } else { 0 });
|
|
||||||
}
|
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
* Ongoing process allocation/free/check
|
|
||||||
******************************************************************************/
|
|
||||||
|
|
||||||
pub fn dc_alloc_ongoing(context: &Context) -> bool {
|
|
||||||
if dc_has_ongoing(context) {
|
|
||||||
warn!(context, "There is already another ongoing process running.",);
|
|
||||||
|
|
||||||
false
|
|
||||||
} else {
|
|
||||||
let s_a = context.running_state.clone();
|
|
||||||
let mut s = s_a.write().unwrap();
|
|
||||||
|
|
||||||
s.ongoing_running = true;
|
|
||||||
s.shall_stop_ongoing = false;
|
|
||||||
|
|
||||||
true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn dc_free_ongoing(context: &Context) {
|
|
||||||
let s_a = context.running_state.clone();
|
|
||||||
let mut s = s_a.write().unwrap();
|
|
||||||
|
|
||||||
s.ongoing_running = false;
|
|
||||||
s.shall_stop_ongoing = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn dc_has_ongoing(context: &Context) -> bool {
|
|
||||||
let s_a = context.running_state.clone();
|
|
||||||
let s = s_a.read().unwrap();
|
|
||||||
|
|
||||||
s.ongoing_running || !s.shall_stop_ongoing
|
|
||||||
}
|
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
* Connect to configured account
|
|
||||||
******************************************************************************/
|
|
||||||
pub fn dc_connect_to_configured_imap(context: &Context, imap: &Imap) -> libc::c_int {
|
|
||||||
let mut ret_connected = 0;
|
|
||||||
|
|
||||||
if imap.is_connected() {
|
|
||||||
ret_connected = 1
|
|
||||||
} else if context
|
|
||||||
.sql
|
|
||||||
.get_config_int(context, "configured")
|
|
||||||
.unwrap_or_default()
|
|
||||||
== 0
|
|
||||||
{
|
|
||||||
warn!(context, "Not configured, cannot connect.",);
|
|
||||||
} else {
|
|
||||||
let param = LoginParam::from_database(context, "configured_");
|
|
||||||
// the trailing underscore is correct
|
|
||||||
|
|
||||||
if imap.connect(context, ¶m) {
|
|
||||||
ret_connected = 2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ret_connected
|
|
||||||
}
|
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
* Configure a Context
|
|
||||||
******************************************************************************/
|
|
||||||
|
|
||||||
/// Signal an ongoing process to stop.
|
|
||||||
pub fn dc_stop_ongoing_process(context: &Context) {
|
|
||||||
let s_a = context.running_state.clone();
|
|
||||||
let mut s = s_a.write().unwrap();
|
|
||||||
|
|
||||||
if s.ongoing_running && !s.shall_stop_ongoing {
|
|
||||||
info!(context, "Signaling the ongoing process to stop ASAP.",);
|
|
||||||
s.shall_stop_ongoing = true;
|
|
||||||
} else {
|
|
||||||
info!(context, "No ongoing process to stop.",);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn read_autoconf_file(context: &Context, url: &str) -> *mut libc::c_char {
|
|
||||||
info!(context, "Testing {} ...", url);
|
|
||||||
|
|
||||||
match reqwest::Client::new()
|
|
||||||
.get(url)
|
|
||||||
.send()
|
|
||||||
.and_then(|mut res| res.text())
|
|
||||||
{
|
|
||||||
Ok(res) => unsafe { res.strdup() },
|
|
||||||
Err(_err) => {
|
|
||||||
info!(context, "Can\'t read file.",);
|
|
||||||
|
|
||||||
std::ptr::null_mut()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
619
src/constants.rs
619
src/constants.rs
@@ -1,139 +1,147 @@
|
|||||||
//! Constants
|
//! Constants
|
||||||
#![allow(non_camel_case_types, dead_code)]
|
|
||||||
|
|
||||||
use deltachat_derive::*;
|
pub const DC_VERSION_STR: &'static [u8; 14] = b"1.0.0-alpha.1\x00";
|
||||||
use lazy_static::lazy_static;
|
|
||||||
|
|
||||||
lazy_static! {
|
pub const DC_MOVE_STATE_MOVING: u32 = 3;
|
||||||
pub static ref DC_VERSION_STR: String = env!("CARGO_PKG_VERSION").to_string();
|
pub const DC_MOVE_STATE_STAY: u32 = 2;
|
||||||
}
|
pub const DC_MOVE_STATE_PENDING: u32 = 1;
|
||||||
|
pub const DC_MOVE_STATE_UNDEFINED: u32 = 0;
|
||||||
#[repr(u8)]
|
|
||||||
#[derive(Debug, Display, Clone, Copy, PartialEq, Eq, FromPrimitive, ToPrimitive, ToSql, FromSql)]
|
|
||||||
pub enum MoveState {
|
|
||||||
Undefined = 0,
|
|
||||||
Pending = 1,
|
|
||||||
Stay = 2,
|
|
||||||
Moving = 3,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for MoveState {
|
|
||||||
fn default() -> Self {
|
|
||||||
MoveState::Undefined
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// some defaults
|
|
||||||
const DC_E2EE_DEFAULT_ENABLED: i32 = 1;
|
|
||||||
pub const DC_MDNS_DEFAULT_ENABLED: i32 = 1;
|
|
||||||
const DC_INBOX_WATCH_DEFAULT: i32 = 1;
|
|
||||||
const DC_SENTBOX_WATCH_DEFAULT: i32 = 1;
|
|
||||||
const DC_MVBOX_WATCH_DEFAULT: i32 = 1;
|
|
||||||
const DC_MVBOX_MOVE_DEFAULT: i32 = 1;
|
|
||||||
|
|
||||||
#[derive(Debug, Display, Clone, Copy, PartialEq, Eq, FromPrimitive, ToPrimitive, FromSql, ToSql)]
|
|
||||||
#[repr(u8)]
|
|
||||||
pub enum Blocked {
|
|
||||||
Not = 0,
|
|
||||||
Manually = 1,
|
|
||||||
Deaddrop = 2,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for Blocked {
|
|
||||||
fn default() -> Self {
|
|
||||||
Blocked::Not
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const DC_IMAP_SEEN: u32 = 0x1;
|
|
||||||
|
|
||||||
pub const DC_HANDSHAKE_CONTINUE_NORMAL_PROCESSING: i32 = 0x01;
|
|
||||||
pub const DC_HANDSHAKE_STOP_NORMAL_PROCESSING: i32 = 0x02;
|
|
||||||
pub const DC_HANDSHAKE_ADD_DELETE_JOB: i32 = 0x04;
|
|
||||||
|
|
||||||
pub const DC_GCL_ARCHIVED_ONLY: usize = 0x01;
|
pub const DC_GCL_ARCHIVED_ONLY: usize = 0x01;
|
||||||
pub const DC_GCL_NO_SPECIALS: usize = 0x02;
|
pub const DC_GCL_NO_SPECIALS: usize = 0x02;
|
||||||
pub const DC_GCL_ADD_ALLDONE_HINT: usize = 0x04;
|
pub const DC_GCL_ADD_ALLDONE_HINT: usize = 0x04;
|
||||||
|
|
||||||
const DC_GCM_ADDDAYMARKER: usize = 0x01;
|
pub const DC_GCM_ADDDAYMARKER: usize = 0x01;
|
||||||
|
|
||||||
pub const DC_GCL_VERIFIED_ONLY: usize = 0x01;
|
pub const DC_GCL_VERIFIED_ONLY: usize = 0x01;
|
||||||
pub const DC_GCL_ADD_SELF: usize = 0x02;
|
pub const DC_GCL_ADD_SELF: usize = 0x02;
|
||||||
|
|
||||||
/// param1 is a directory where the keys are written to
|
/// param1 is a directory where the keys are written to
|
||||||
const DC_IMEX_EXPORT_SELF_KEYS: usize = 1;
|
pub const DC_IMEX_EXPORT_SELF_KEYS: usize = 1;
|
||||||
/// param1 is a directory where the keys are searched in and read from
|
/// param1 is a directory where the keys are searched in and read from
|
||||||
const DC_IMEX_IMPORT_SELF_KEYS: usize = 2;
|
pub const DC_IMEX_IMPORT_SELF_KEYS: usize = 2;
|
||||||
/// param1 is a directory where the backup is written to
|
/// param1 is a directory where the backup is written to
|
||||||
const DC_IMEX_EXPORT_BACKUP: usize = 11;
|
pub const DC_IMEX_EXPORT_BACKUP: usize = 11;
|
||||||
/// param1 is the file with the backup to import
|
/// param1 is the file with the backup to import
|
||||||
const DC_IMEX_IMPORT_BACKUP: usize = 12;
|
pub const DC_IMEX_IMPORT_BACKUP: usize = 12;
|
||||||
|
|
||||||
|
/// id=contact
|
||||||
|
pub const DC_QR_ASK_VERIFYCONTACT: usize = 200;
|
||||||
|
/// text1=groupname
|
||||||
|
pub const DC_QR_ASK_VERIFYGROUP: usize = 202;
|
||||||
|
/// id=contact
|
||||||
|
pub const DC_QR_FPR_OK: usize = 210;
|
||||||
|
/// id=contact
|
||||||
|
pub const DC_QR_FPR_MISMATCH: usize = 220;
|
||||||
|
/// test1=formatted fingerprint
|
||||||
|
pub const DC_QR_FPR_WITHOUT_ADDR: usize = 230;
|
||||||
|
/// id=contact
|
||||||
|
pub const DC_QR_ADDR: usize = 320;
|
||||||
|
/// text1=text
|
||||||
|
pub const DC_QR_TEXT: usize = 330;
|
||||||
|
/// text1=URL
|
||||||
|
pub const DC_QR_URL: usize = 332;
|
||||||
|
/// text1=error string
|
||||||
|
pub const DC_QR_ERROR: usize = 400;
|
||||||
|
|
||||||
/// virtual chat showing all messages belonging to chats flagged with chats.blocked=2
|
/// virtual chat showing all messages belonging to chats flagged with chats.blocked=2
|
||||||
pub(crate) const DC_CHAT_ID_DEADDROP: u32 = 1;
|
pub const DC_CHAT_ID_DEADDROP: usize = 1;
|
||||||
/// messages that should be deleted get this chat_id; the messages are deleted from the working thread later then. This is also needed as rfc724_mid should be preset as long as the message is not deleted on the server (otherwise it is downloaded again)
|
/// messages that should be deleted get this chat_id; the messages are deleted from the working thread later then. This is also needed as rfc724_mid should be preset as long as the message is not deleted on the server (otherwise it is downloaded again)
|
||||||
pub const DC_CHAT_ID_TRASH: u32 = 3;
|
pub const DC_CHAT_ID_TRASH: usize = 3;
|
||||||
/// a message is just in creation but not yet assigned to a chat (eg. we may need the message ID to set up blobs; this avoids unready message to be sent and shown)
|
/// a message is just in creation but not yet assigned to a chat (eg. we may need the message ID to set up blobs; this avoids unready message to be sent and shown)
|
||||||
const DC_CHAT_ID_MSGS_IN_CREATION: u32 = 4;
|
pub const DC_CHAT_ID_MSGS_IN_CREATION: usize = 4;
|
||||||
/// virtual chat showing all messages flagged with msgs.starred=2
|
/// virtual chat showing all messages flagged with msgs.starred=2
|
||||||
pub const DC_CHAT_ID_STARRED: u32 = 5;
|
pub const DC_CHAT_ID_STARRED: usize = 5;
|
||||||
/// only an indicator in a chatlist
|
/// only an indicator in a chatlist
|
||||||
pub const DC_CHAT_ID_ARCHIVED_LINK: u32 = 6;
|
pub const DC_CHAT_ID_ARCHIVED_LINK: usize = 6;
|
||||||
/// only an indicator in a chatlist
|
/// only an indicator in a chatlist
|
||||||
pub const DC_CHAT_ID_ALLDONE_HINT: u32 = 7;
|
pub const DC_CHAT_ID_ALLDONE_HINT: usize = 7;
|
||||||
/// larger chat IDs are "real" chats, their messages are "real" messages.
|
/// larger chat IDs are "real" chats, their messages are "real" messages.
|
||||||
pub const DC_CHAT_ID_LAST_SPECIAL: u32 = 9;
|
pub const DC_CHAT_ID_LAST_SPECIAL: usize = 9;
|
||||||
|
|
||||||
#[derive(
|
pub const DC_CHAT_TYPE_UNDEFINED: usize = 0;
|
||||||
Debug,
|
pub const DC_CHAT_TYPE_SINGLE: usize = 100;
|
||||||
Display,
|
pub const DC_CHAT_TYPE_GROUP: usize = 120;
|
||||||
Clone,
|
pub const DC_CHAT_TYPE_VERIFIED_GROUP: usize = 130;
|
||||||
Copy,
|
|
||||||
PartialEq,
|
|
||||||
Eq,
|
|
||||||
FromPrimitive,
|
|
||||||
ToPrimitive,
|
|
||||||
FromSql,
|
|
||||||
ToSql,
|
|
||||||
IntoStaticStr,
|
|
||||||
)]
|
|
||||||
#[repr(u32)]
|
|
||||||
pub enum Chattype {
|
|
||||||
Undefined = 0,
|
|
||||||
Single = 100,
|
|
||||||
Group = 120,
|
|
||||||
VerifiedGroup = 130,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for Chattype {
|
pub const DC_MSG_ID_MARKER1: usize = 1;
|
||||||
fn default() -> Self {
|
pub const DC_MSG_ID_DAYMARKER: usize = 9;
|
||||||
Chattype::Undefined
|
pub const DC_MSG_ID_LAST_SPECIAL: usize = 9;
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const DC_MSG_ID_MARKER1: u32 = 1;
|
pub const DC_STATE_UNDEFINED: usize = 0;
|
||||||
const DC_MSG_ID_DAYMARKER: u32 = 9;
|
pub const DC_STATE_IN_FRESH: usize = 10;
|
||||||
pub const DC_MSG_ID_LAST_SPECIAL: u32 = 9;
|
pub const DC_STATE_IN_NOTICED: usize = 13;
|
||||||
|
pub const DC_STATE_IN_SEEN: usize = 16;
|
||||||
|
pub const DC_STATE_OUT_PREPARING: usize = 18;
|
||||||
|
pub const DC_STATE_OUT_DRAFT: usize = 19;
|
||||||
|
pub const DC_STATE_OUT_PENDING: usize = 20;
|
||||||
|
pub const DC_STATE_OUT_FAILED: usize = 24;
|
||||||
|
/// to check if a mail was sent, use dc_msg_is_sent()
|
||||||
|
pub const DC_STATE_OUT_DELIVERED: usize = 26;
|
||||||
|
pub const DC_STATE_OUT_MDN_RCVD: usize = 28;
|
||||||
|
|
||||||
/// approx. max. length returned by dc_msg_get_text()
|
/// approx. max. lenght returned by dc_msg_get_text()
|
||||||
const DC_MAX_GET_TEXT_LEN: usize = 30000;
|
pub const DC_MAX_GET_TEXT_LEN: usize = 30000;
|
||||||
/// approx. max. length returned by dc_get_msg_info()
|
/// approx. max. lenght returned by dc_get_msg_info()
|
||||||
const DC_MAX_GET_INFO_LEN: usize = 100000;
|
pub const DC_MAX_GET_INFO_LEN: usize = 100000;
|
||||||
|
|
||||||
pub const DC_CONTACT_ID_UNDEFINED: u32 = 0;
|
pub const DC_CONTACT_ID_SELF: usize = 1;
|
||||||
pub const DC_CONTACT_ID_SELF: u32 = 1;
|
pub const DC_CONTACT_ID_DEVICE: usize = 2;
|
||||||
const DC_CONTACT_ID_DEVICE: u32 = 2;
|
pub const DC_CONTACT_ID_LAST_SPECIAL: usize = 9;
|
||||||
pub const DC_CONTACT_ID_LAST_SPECIAL: u32 = 9;
|
|
||||||
|
pub const DC_TEXT1_DRAFT: usize = 1;
|
||||||
|
pub const DC_TEXT1_USERNAME: usize = 2;
|
||||||
|
pub const DC_TEXT1_SELF: usize = 3;
|
||||||
|
|
||||||
pub const DC_CREATE_MVBOX: usize = 1;
|
pub const DC_CREATE_MVBOX: usize = 1;
|
||||||
|
|
||||||
|
/// Text message.
|
||||||
|
/// The text of the message is set using dc_msg_set_text()
|
||||||
|
/// and retrieved with dc_msg_get_text().
|
||||||
|
pub const DC_MSG_TEXT: usize = 10;
|
||||||
|
|
||||||
|
/// Image message.
|
||||||
|
/// If the image is an animated GIF, the type DC_MSG_GIF should be used.
|
||||||
|
/// File, width and height are set via dc_msg_set_file(), dc_msg_set_dimension
|
||||||
|
/// and retrieved via dc_msg_set_file(), dc_msg_set_dimension().
|
||||||
|
pub const DC_MSG_IMAGE: usize = 20;
|
||||||
|
|
||||||
|
/// Animated GIF message.
|
||||||
|
/// File, width and height are set via dc_msg_set_file(), dc_msg_set_dimension()
|
||||||
|
/// and retrieved via dc_msg_get_file(), dc_msg_get_width(), dc_msg_get_height().
|
||||||
|
pub const DC_MSG_GIF: usize = 21;
|
||||||
|
|
||||||
|
/// Message containing an Audio file.
|
||||||
|
/// File and duration are set via dc_msg_set_file(), dc_msg_set_duration()
|
||||||
|
/// and retrieved via dc_msg_get_file(), dc_msg_get_duration().
|
||||||
|
pub const DC_MSG_AUDIO: usize = 40;
|
||||||
|
|
||||||
|
/// A voice message that was directly recorded by the user.
|
||||||
|
/// For all other audio messages, the type #DC_MSG_AUDIO should be used.
|
||||||
|
/// File and duration are set via dc_msg_set_file(), dc_msg_set_duration()
|
||||||
|
/// and retieved via dc_msg_get_file(), dc_msg_get_duration()
|
||||||
|
pub const DC_MSG_VOICE: usize = 41;
|
||||||
|
|
||||||
|
/// Video messages.
|
||||||
|
/// File, width, height and durarion
|
||||||
|
/// are set via dc_msg_set_file(), dc_msg_set_dimension(), dc_msg_set_duration()
|
||||||
|
/// and retrieved via
|
||||||
|
/// dc_msg_get_file(), dc_msg_get_width(),
|
||||||
|
/// dc_msg_get_height(), dc_msg_get_duration().
|
||||||
|
pub const DC_MSG_VIDEO: usize = 50;
|
||||||
|
|
||||||
|
/// Message containing any file, eg. a PDF.
|
||||||
|
/// The file is set via dc_msg_set_file()
|
||||||
|
/// and retrieved via dc_msg_get_file().
|
||||||
|
pub const DC_MSG_FILE: usize = 60;
|
||||||
|
|
||||||
// Flags for configuring IMAP and SMTP servers.
|
// Flags for configuring IMAP and SMTP servers.
|
||||||
// These flags are optional
|
// These flags are optional
|
||||||
// and may be set together with the username, password etc.
|
// and may be set together with the username, password etc.
|
||||||
// via dc_set_config() using the key "server_flags".
|
// via dc_set_config() using the key "server_flags".
|
||||||
|
|
||||||
/// Force OAuth2 authorization. This flag does not skip automatic configuration.
|
/// Force OAuth2 authorization. This flag does not skip automatic configuration.
|
||||||
/// Before calling configure() with DC_LP_AUTH_OAUTH2 set,
|
/// Before calling dc_configure() with DC_LP_AUTH_OAUTH2 set,
|
||||||
/// the user has to confirm access at the URL returned by dc_get_oauth2_url().
|
/// the user has to confirm access at the URL returned by dc_get_oauth2_url().
|
||||||
pub const DC_LP_AUTH_OAUTH2: usize = 0x2;
|
pub const DC_LP_AUTH_OAUTH2: usize = 0x2;
|
||||||
|
|
||||||
@@ -165,146 +173,315 @@ pub const DC_LP_SMTP_SOCKET_SSL: usize = 0x20000;
|
|||||||
/// If this flag is set, automatic configuration is skipped.
|
/// If this flag is set, automatic configuration is skipped.
|
||||||
pub const DC_LP_SMTP_SOCKET_PLAIN: usize = 0x40000;
|
pub const DC_LP_SMTP_SOCKET_PLAIN: usize = 0x40000;
|
||||||
|
|
||||||
/// if none of these flags are set, the default is chosen
|
/// if none of these flags are set, the default is choosen
|
||||||
pub const DC_LP_AUTH_FLAGS: usize = (DC_LP_AUTH_OAUTH2 | DC_LP_AUTH_NORMAL);
|
pub const DC_LP_AUTH_FLAGS: usize = (DC_LP_AUTH_OAUTH2 | DC_LP_AUTH_NORMAL);
|
||||||
/// if none of these flags are set, the default is chosen
|
/// if none of these flags are set, the default is choosen
|
||||||
pub const DC_LP_IMAP_SOCKET_FLAGS: usize =
|
pub const DC_LP_IMAP_SOCKET_FLAGS: usize =
|
||||||
(DC_LP_IMAP_SOCKET_STARTTLS | DC_LP_IMAP_SOCKET_SSL | DC_LP_IMAP_SOCKET_PLAIN);
|
(DC_LP_IMAP_SOCKET_STARTTLS | DC_LP_IMAP_SOCKET_SSL | DC_LP_IMAP_SOCKET_PLAIN);
|
||||||
/// if none of these flags are set, the default is chosen
|
/// if none of these flags are set, the default is choosen
|
||||||
pub const DC_LP_SMTP_SOCKET_FLAGS: usize =
|
pub const DC_LP_SMTP_SOCKET_FLAGS: usize =
|
||||||
(DC_LP_SMTP_SOCKET_STARTTLS | DC_LP_SMTP_SOCKET_SSL | DC_LP_SMTP_SOCKET_PLAIN);
|
(DC_LP_SMTP_SOCKET_STARTTLS | DC_LP_SMTP_SOCKET_SSL | DC_LP_SMTP_SOCKET_PLAIN);
|
||||||
|
|
||||||
// QR code scanning (view from Bob, the joiner)
|
|
||||||
pub const DC_VC_AUTH_REQUIRED: i32 = 2;
|
|
||||||
pub const DC_VC_CONTACT_CONFIRM: i32 = 6;
|
|
||||||
pub const DC_BOB_ERROR: i32 = 0;
|
|
||||||
pub const DC_BOB_SUCCESS: i32 = 1;
|
|
||||||
|
|
||||||
#[derive(Debug, Display, Clone, Copy, PartialEq, Eq, FromPrimitive, ToPrimitive, FromSql, ToSql)]
|
|
||||||
#[repr(i32)]
|
|
||||||
pub enum Viewtype {
|
|
||||||
Unknown = 0,
|
|
||||||
/// Text message.
|
|
||||||
/// The text of the message is set using dc_msg_set_text()
|
|
||||||
/// and retrieved with dc_msg_get_text().
|
|
||||||
Text = 10,
|
|
||||||
|
|
||||||
/// Image message.
|
|
||||||
/// If the image is an animated GIF, the type DC_MSG_GIF should be used.
|
|
||||||
/// File, width and height are set via dc_msg_set_file(), dc_msg_set_dimension
|
|
||||||
/// and retrieved via dc_msg_set_file(), dc_msg_set_dimension().
|
|
||||||
Image = 20,
|
|
||||||
|
|
||||||
/// Animated GIF message.
|
|
||||||
/// File, width and height are set via dc_msg_set_file(), dc_msg_set_dimension()
|
|
||||||
/// and retrieved via dc_msg_get_file(), dc_msg_get_width(), dc_msg_get_height().
|
|
||||||
Gif = 21,
|
|
||||||
|
|
||||||
/// Message containing an Audio file.
|
|
||||||
/// File and duration are set via dc_msg_set_file(), dc_msg_set_duration()
|
|
||||||
/// and retrieved via dc_msg_get_file(), dc_msg_get_duration().
|
|
||||||
Audio = 40,
|
|
||||||
|
|
||||||
/// A voice message that was directly recorded by the user.
|
|
||||||
/// For all other audio messages, the type #DC_MSG_AUDIO should be used.
|
|
||||||
/// File and duration are set via dc_msg_set_file(), dc_msg_set_duration()
|
|
||||||
/// and retrieved via dc_msg_get_file(), dc_msg_get_duration()
|
|
||||||
Voice = 41,
|
|
||||||
|
|
||||||
/// Video messages.
|
|
||||||
/// File, width, height and durarion
|
|
||||||
/// are set via dc_msg_set_file(), dc_msg_set_dimension(), dc_msg_set_duration()
|
|
||||||
/// and retrieved via
|
|
||||||
/// dc_msg_get_file(), dc_msg_get_width(),
|
|
||||||
/// dc_msg_get_height(), dc_msg_get_duration().
|
|
||||||
Video = 50,
|
|
||||||
|
|
||||||
/// Message containing any file, eg. a PDF.
|
|
||||||
/// The file is set via dc_msg_set_file()
|
|
||||||
/// and retrieved via dc_msg_get_file().
|
|
||||||
File = 60,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for Viewtype {
|
|
||||||
fn default() -> Self {
|
|
||||||
Viewtype::Unknown
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn derive_display_works_as_expected() {
|
|
||||||
assert_eq!(format!("{}", Viewtype::Audio), "Audio");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// These constants are used as events
|
// These constants are used as events
|
||||||
// reported to the callback given to dc_context_new().
|
// reported to the callback given to dc_context_new().
|
||||||
// If you do not want to handle an event, it is always safe to return 0,
|
// If you do not want to handle an event, it is always safe to return 0,
|
||||||
// so there is no need to add a "case" for every event.
|
// so there is no need to add a "case" for every event.
|
||||||
|
|
||||||
const DC_EVENT_FILE_COPIED: usize = 2055; // deprecated;
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, FromPrimitive, ToPrimitive)]
|
||||||
const DC_EVENT_IS_OFFLINE: usize = 2081; // deprecated;
|
#[repr(u32)]
|
||||||
const DC_ERROR_SEE_STRING: usize = 0; // deprecated;
|
pub enum Event {
|
||||||
const DC_ERROR_SELF_NOT_IN_GROUP: usize = 1; // deprecated;
|
/// The library-user may write an informational string to the log.
|
||||||
const DC_STR_SELFNOTINGRP: usize = 21; // deprecated;
|
/// Passed to the callback given to dc_context_new().
|
||||||
|
/// This event should not be reported to the end-user using a popup or something like that.
|
||||||
|
/// @param data1 0
|
||||||
|
/// @param data2 (const char*) Info string in english language.
|
||||||
|
/// Must not be free()'d or modified and is valid only until the callback returns.
|
||||||
|
/// @return 0
|
||||||
|
INFO = 100,
|
||||||
|
|
||||||
|
/// Emitted when SMTP connection is established and login was successful.
|
||||||
|
///
|
||||||
|
/// @param data1 0
|
||||||
|
/// @param data2 (const char*) Info string in english language.
|
||||||
|
/// Must not be free()'d or modified and is valid only until the callback returns.
|
||||||
|
/// @return 0
|
||||||
|
SMTP_CONNECTED = 101,
|
||||||
|
|
||||||
|
/// Emitted when IMAP connection is established and login was successful.
|
||||||
|
///
|
||||||
|
/// @param data1 0
|
||||||
|
/// @param data2 (const char*) Info string in english language.
|
||||||
|
/// Must not be free()'d or modified and is valid only until the callback returns.
|
||||||
|
/// @return 0
|
||||||
|
IMAP_CONNECTED = 102,
|
||||||
|
|
||||||
|
/// Emitted when a message was successfully sent to the SMTP server.
|
||||||
|
///
|
||||||
|
/// @param data1 0
|
||||||
|
/// @param data2 (const char*) Info string in english language.
|
||||||
|
/// Must not be free()'d or modified and is valid only until the callback returns.
|
||||||
|
/// @return 0
|
||||||
|
SMTP_MESSAGE_SENT = 103,
|
||||||
|
|
||||||
|
/// The library-user should write a warning string to the log.
|
||||||
|
/// Passed to the callback given to dc_context_new().
|
||||||
|
///
|
||||||
|
/// This event should not be reported to the end-user using a popup or something like that.
|
||||||
|
///
|
||||||
|
/// @param data1 0
|
||||||
|
/// @param data2 (const char*) Warning string in english language.
|
||||||
|
/// Must not be free()'d or modified and is valid only until the callback returns.
|
||||||
|
/// @return 0
|
||||||
|
WARNING = 300,
|
||||||
|
|
||||||
|
/// The library-user should report an error to the end-user.
|
||||||
|
/// Passed to the callback given to dc_context_new().
|
||||||
|
///
|
||||||
|
/// As most things are asynchrounous, things may go wrong at any time and the user
|
||||||
|
/// should not be disturbed by a dialog or so. Instead, use a bubble or so.
|
||||||
|
///
|
||||||
|
/// However, for ongoing processes (eg. dc_configure())
|
||||||
|
/// or for functions that are expected to fail (eg. dc_continue_key_transfer())
|
||||||
|
/// it might be better to delay showing these events until the function has really
|
||||||
|
/// failed (returned false). It should be sufficient to report only the _last_ error
|
||||||
|
/// in a messasge box then.
|
||||||
|
///
|
||||||
|
/// @param data1 0
|
||||||
|
/// @param data2 (const char*) Error string, always set, never NULL. Frequent error strings are
|
||||||
|
/// localized using #DC_EVENT_GET_STRING, however, most error strings will be in english language.
|
||||||
|
/// Must not be free()'d or modified and is valid only until the callback returns.
|
||||||
|
/// @return 0
|
||||||
|
ERROR = 400,
|
||||||
|
|
||||||
|
/// An action cannot be performed because there is no network available.
|
||||||
|
///
|
||||||
|
/// The library will typically try over after a some time
|
||||||
|
/// and when dc_maybe_network() is called.
|
||||||
|
///
|
||||||
|
/// Network errors should be reported to users in a non-disturbing way,
|
||||||
|
/// however, as network errors may come in a sequence,
|
||||||
|
/// it is not useful to raise each an every error to the user.
|
||||||
|
/// For this purpose, data1 is set to 1 if the error is probably worth reporting.
|
||||||
|
///
|
||||||
|
/// Moreover, if the UI detects that the device is offline,
|
||||||
|
/// it is probably more useful to report this to the user
|
||||||
|
/// instread of the string from data2.
|
||||||
|
///
|
||||||
|
/// @param data1 (int) 1=first/new network error, should be reported the user;
|
||||||
|
/// 0=subsequent network error, should be logged only
|
||||||
|
/// @param data2 (const char*) Error string, always set, never NULL.
|
||||||
|
/// Must not be free()'d or modified and is valid only until the callback returns.
|
||||||
|
/// @return 0
|
||||||
|
ERROR_NETWORK = 401,
|
||||||
|
|
||||||
|
/// An action cannot be performed because the user is not in the group.
|
||||||
|
/// Reported eg. after a call to
|
||||||
|
/// dc_set_chat_name(), dc_set_chat_profile_image(),
|
||||||
|
/// dc_add_contact_to_chat(), dc_remove_contact_from_chat(),
|
||||||
|
/// dc_send_text_msg() or another sending function.
|
||||||
|
///
|
||||||
|
/// @param data1 0
|
||||||
|
/// @param data2 (const char*) Info string in english language.
|
||||||
|
/// Must not be free()'d or modified
|
||||||
|
/// and is valid only until the callback returns.
|
||||||
|
/// @return 0
|
||||||
|
ERROR_SELF_NOT_IN_GROUP = 410,
|
||||||
|
|
||||||
|
/// Messages or chats changed. One or more messages or chats changed for various
|
||||||
|
/// reasons in the database:
|
||||||
|
/// - Messages sent, received or removed
|
||||||
|
/// - Chats created, deleted or archived
|
||||||
|
/// - A draft has been set
|
||||||
|
///
|
||||||
|
/// @param data1 (int) chat_id for single added messages
|
||||||
|
/// @param data2 (int) msg_id for single added messages
|
||||||
|
/// @return 0
|
||||||
|
MSGS_CHANGED = 2000,
|
||||||
|
|
||||||
|
/// There is a fresh message. Typically, the user will show an notification
|
||||||
|
/// when receiving this message.
|
||||||
|
///
|
||||||
|
/// There is no extra #DC_EVENT_MSGS_CHANGED event send together with this event.
|
||||||
|
///
|
||||||
|
/// @param data1 (int) chat_id
|
||||||
|
/// @param data2 (int) msg_id
|
||||||
|
/// @return 0
|
||||||
|
INCOMING_MSG = 2005,
|
||||||
|
|
||||||
|
/// A single message is sent successfully. State changed from DC_STATE_OUT_PENDING to
|
||||||
|
/// DC_STATE_OUT_DELIVERED, see dc_msg_get_state().
|
||||||
|
///
|
||||||
|
/// @param data1 (int) chat_id
|
||||||
|
/// @param data2 (int) msg_id
|
||||||
|
/// @return 0
|
||||||
|
MSG_DELIVERED = 2010,
|
||||||
|
|
||||||
|
/// A single message could not be sent. State changed from DC_STATE_OUT_PENDING or DC_STATE_OUT_DELIVERED to
|
||||||
|
/// DC_STATE_OUT_FAILED, see dc_msg_get_state().
|
||||||
|
///
|
||||||
|
/// @param data1 (int) chat_id
|
||||||
|
/// @param data2 (int) msg_id
|
||||||
|
/// @return 0
|
||||||
|
MSG_FAILED = 2012,
|
||||||
|
|
||||||
|
/// A single message is read by the receiver. State changed from DC_STATE_OUT_DELIVERED to
|
||||||
|
/// DC_STATE_OUT_MDN_RCVD, see dc_msg_get_state().
|
||||||
|
///
|
||||||
|
/// @param data1 (int) chat_id
|
||||||
|
/// @param data2 (int) msg_id
|
||||||
|
/// @return 0
|
||||||
|
MSG_READ = 2015,
|
||||||
|
|
||||||
|
/// Chat changed. The name or the image of a chat group was changed or members were added or removed.
|
||||||
|
/// Or the verify state of a chat has changed.
|
||||||
|
/// See dc_set_chat_name(), dc_set_chat_profile_image(), dc_add_contact_to_chat()
|
||||||
|
/// and dc_remove_contact_from_chat().
|
||||||
|
///
|
||||||
|
/// @param data1 (int) chat_id
|
||||||
|
/// @param data2 0
|
||||||
|
/// @return 0
|
||||||
|
CHAT_MODIFIED = 2020,
|
||||||
|
|
||||||
|
/// Contact(s) created, renamed, blocked or deleted.
|
||||||
|
///
|
||||||
|
/// @param data1 (int) If not 0, this is the contact_id of an added contact that should be selected.
|
||||||
|
/// @param data2 0
|
||||||
|
/// @return 0
|
||||||
|
CONTACTS_CHANGED = 2030,
|
||||||
|
|
||||||
|
/// Location of one or more contact has changed.
|
||||||
|
///
|
||||||
|
/// @param data1 (int) contact_id of the contact for which the location has changed.
|
||||||
|
/// If the locations of several contacts have been changed,
|
||||||
|
/// eg. after calling dc_delete_all_locations(), this parameter is set to 0.
|
||||||
|
/// @param data2 0
|
||||||
|
/// @return 0
|
||||||
|
LOCATION_CHANGED = 2035,
|
||||||
|
|
||||||
|
/// Inform about the configuration progress started by dc_configure().
|
||||||
|
///
|
||||||
|
/// @param data1 (int) 0=error, 1-999=progress in permille, 1000=success and done
|
||||||
|
/// @param data2 0
|
||||||
|
/// @return 0
|
||||||
|
CONFIGURE_PROGRESS = 2041,
|
||||||
|
|
||||||
|
/// Inform about the import/export progress started by dc_imex().
|
||||||
|
///
|
||||||
|
/// @param data1 (int) 0=error, 1-999=progress in permille, 1000=success and done
|
||||||
|
/// @param data2 0
|
||||||
|
/// @return 0
|
||||||
|
IMEX_PROGRESS = 2051,
|
||||||
|
|
||||||
|
/// A file has been exported. A file has been written by dc_imex().
|
||||||
|
/// This event may be sent multiple times by a single call to dc_imex().
|
||||||
|
///
|
||||||
|
/// A typical purpose for a handler of this event may be to make the file public to some system
|
||||||
|
/// services.
|
||||||
|
///
|
||||||
|
/// @param data1 (const char*) Path and file name.
|
||||||
|
/// Must not be free()'d or modified and is valid only until the callback returns.
|
||||||
|
/// @param data2 0
|
||||||
|
/// @return 0
|
||||||
|
IMEX_FILE_WRITTEN = 2052,
|
||||||
|
|
||||||
|
/// Progress information of a secure-join handshake from the view of the inviter
|
||||||
|
/// (Alice, the person who shows the QR code).
|
||||||
|
///
|
||||||
|
/// These events are typically sent after a joiner has scanned the QR code
|
||||||
|
/// generated by dc_get_securejoin_qr().
|
||||||
|
///
|
||||||
|
/// @param data1 (int) ID of the contact that wants to join.
|
||||||
|
/// @param data2 (int) Progress as:
|
||||||
|
/// 300=vg-/vc-request received, typically shown as "bob@addr joins".
|
||||||
|
/// 600=vg-/vc-request-with-auth received, vg-member-added/vc-contact-confirm sent, typically shown as "bob@addr verified".
|
||||||
|
/// 800=vg-member-added-received received, shown as "bob@addr securely joined GROUP", only sent for the verified-group-protocol.
|
||||||
|
/// 1000=Protocol finished for this contact.
|
||||||
|
/// @return 0
|
||||||
|
SECUREJOIN_INVITER_PROGRESS = 2060,
|
||||||
|
|
||||||
|
/// Progress information of a secure-join handshake from the view of the joiner
|
||||||
|
/// (Bob, the person who scans the QR code).
|
||||||
|
/// The events are typically sent while dc_join_securejoin(), which
|
||||||
|
/// may take some time, is executed.
|
||||||
|
/// @param data1 (int) ID of the inviting contact.
|
||||||
|
/// @param data2 (int) Progress as:
|
||||||
|
/// 400=vg-/vc-request-with-auth sent, typically shown as "alice@addr verified, introducing myself."
|
||||||
|
/// (Bob has verified alice and waits until Alice does the same for him)
|
||||||
|
/// @return 0
|
||||||
|
SECUREJOIN_JOINER_PROGRESS = 2061,
|
||||||
|
|
||||||
|
// the following events are functions that should be provided by the frontends
|
||||||
|
/// Requeste a localized string from the frontend.
|
||||||
|
/// @param data1 (int) ID of the string to request, one of the DC_STR_/// constants.
|
||||||
|
/// @param data2 (int) The count. If the requested string contains a placeholder for a numeric value,
|
||||||
|
/// the ui may use this value to return different strings on different plural forms.
|
||||||
|
/// @return (const char*) Null-terminated UTF-8 string.
|
||||||
|
/// The string will be free()'d by the core,
|
||||||
|
/// so it must be allocated using malloc() or a compatible function.
|
||||||
|
/// Return 0 if the ui cannot provide the requested string
|
||||||
|
/// the core will use a default string in english language then.
|
||||||
|
GET_STRING = 2091,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const DC_EVENT_FILE_COPIED: usize = 2055; // deprecated;
|
||||||
|
pub const DC_EVENT_IS_OFFLINE: usize = 2081; // deprecated;
|
||||||
|
pub const DC_ERROR_SEE_STRING: usize = 0; // deprecated;
|
||||||
|
pub const DC_ERROR_SELF_NOT_IN_GROUP: usize = 1; // deprecated;
|
||||||
|
pub const DC_STR_SELFNOTINGRP: usize = 21; // deprecated;
|
||||||
|
|
||||||
/// Values for dc_get|set_config("show_emails")
|
/// Values for dc_get|set_config("show_emails")
|
||||||
const DC_SHOW_EMAILS_OFF: usize = 0;
|
pub const DC_SHOW_EMAILS_OFF: usize = 0;
|
||||||
const DC_SHOW_EMAILS_ACCEPTED_CONTACTS: usize = 1;
|
pub const DC_SHOW_EMAILS_ACCEPTED_CONTACTS: usize = 1;
|
||||||
const DC_SHOW_EMAILS_ALL: usize = 2;
|
pub const DC_SHOW_EMAILS_ALL: usize = 2;
|
||||||
|
|
||||||
// TODO: Strings need some doumentation about used placeholders.
|
// TODO: Strings need some doumentation about used placeholders.
|
||||||
// These constants are used to request strings using #DC_EVENT_GET_STRING.
|
// These constants are used to request strings using #DC_EVENT_GET_STRING.
|
||||||
|
|
||||||
const DC_STR_NOMESSAGES: usize = 1;
|
pub const DC_STR_NOMESSAGES: usize = 1;
|
||||||
const DC_STR_SELF: usize = 2;
|
pub const DC_STR_SELF: usize = 2;
|
||||||
const DC_STR_DRAFT: usize = 3;
|
pub const DC_STR_DRAFT: usize = 3;
|
||||||
const DC_STR_MEMBER: usize = 4;
|
pub const DC_STR_MEMBER: usize = 4;
|
||||||
const DC_STR_CONTACT: usize = 6;
|
pub const DC_STR_CONTACT: usize = 6;
|
||||||
const DC_STR_VOICEMESSAGE: usize = 7;
|
pub const DC_STR_VOICEMESSAGE: usize = 7;
|
||||||
const DC_STR_DEADDROP: usize = 8;
|
pub const DC_STR_DEADDROP: usize = 8;
|
||||||
const DC_STR_IMAGE: usize = 9;
|
pub const DC_STR_IMAGE: usize = 9;
|
||||||
const DC_STR_VIDEO: usize = 10;
|
pub const DC_STR_VIDEO: usize = 10;
|
||||||
const DC_STR_AUDIO: usize = 11;
|
pub const DC_STR_AUDIO: usize = 11;
|
||||||
const DC_STR_FILE: usize = 12;
|
pub const DC_STR_FILE: usize = 12;
|
||||||
const DC_STR_STATUSLINE: usize = 13;
|
pub const DC_STR_STATUSLINE: usize = 13;
|
||||||
const DC_STR_NEWGROUPDRAFT: usize = 14;
|
pub const DC_STR_NEWGROUPDRAFT: usize = 14;
|
||||||
const DC_STR_MSGGRPNAME: usize = 15;
|
pub const DC_STR_MSGGRPNAME: usize = 15;
|
||||||
const DC_STR_MSGGRPIMGCHANGED: usize = 16;
|
pub const DC_STR_MSGGRPIMGCHANGED: usize = 16;
|
||||||
const DC_STR_MSGADDMEMBER: usize = 17;
|
pub const DC_STR_MSGADDMEMBER: usize = 17;
|
||||||
const DC_STR_MSGDELMEMBER: usize = 18;
|
pub const DC_STR_MSGDELMEMBER: usize = 18;
|
||||||
const DC_STR_MSGGROUPLEFT: usize = 19;
|
pub const DC_STR_MSGGROUPLEFT: usize = 19;
|
||||||
const DC_STR_GIF: usize = 23;
|
pub const DC_STR_GIF: usize = 23;
|
||||||
const DC_STR_ENCRYPTEDMSG: usize = 24;
|
pub const DC_STR_ENCRYPTEDMSG: usize = 24;
|
||||||
const DC_STR_E2E_AVAILABLE: usize = 25;
|
pub const DC_STR_E2E_AVAILABLE: usize = 25;
|
||||||
const DC_STR_ENCR_TRANSP: usize = 27;
|
pub const DC_STR_ENCR_TRANSP: usize = 27;
|
||||||
const DC_STR_ENCR_NONE: usize = 28;
|
pub const DC_STR_ENCR_NONE: usize = 28;
|
||||||
const DC_STR_CANTDECRYPT_MSG_BODY: usize = 29;
|
pub const DC_STR_CANTDECRYPT_MSG_BODY: usize = 29;
|
||||||
const DC_STR_FINGERPRINTS: usize = 30;
|
pub const DC_STR_FINGERPRINTS: usize = 30;
|
||||||
const DC_STR_READRCPT: usize = 31;
|
pub const DC_STR_READRCPT: usize = 31;
|
||||||
const DC_STR_READRCPT_MAILBODY: usize = 32;
|
pub const DC_STR_READRCPT_MAILBODY: usize = 32;
|
||||||
const DC_STR_MSGGRPIMGDELETED: usize = 33;
|
pub const DC_STR_MSGGRPIMGDELETED: usize = 33;
|
||||||
const DC_STR_E2E_PREFERRED: usize = 34;
|
pub const DC_STR_E2E_PREFERRED: usize = 34;
|
||||||
const DC_STR_CONTACT_VERIFIED: usize = 35;
|
pub const DC_STR_CONTACT_VERIFIED: usize = 35;
|
||||||
const DC_STR_CONTACT_NOT_VERIFIED: usize = 36;
|
pub const DC_STR_CONTACT_NOT_VERIFIED: usize = 36;
|
||||||
const DC_STR_CONTACT_SETUP_CHANGED: usize = 37;
|
pub const DC_STR_CONTACT_SETUP_CHANGED: usize = 37;
|
||||||
const DC_STR_ARCHIVEDCHATS: usize = 40;
|
pub const DC_STR_ARCHIVEDCHATS: usize = 40;
|
||||||
const DC_STR_STARREDMSGS: usize = 41;
|
pub const DC_STR_STARREDMSGS: usize = 41;
|
||||||
const DC_STR_AC_SETUP_MSG_SUBJECT: usize = 42;
|
pub const DC_STR_AC_SETUP_MSG_SUBJECT: usize = 42;
|
||||||
const DC_STR_AC_SETUP_MSG_BODY: usize = 43;
|
pub const DC_STR_AC_SETUP_MSG_BODY: usize = 43;
|
||||||
const DC_STR_SELFTALK_SUBTITLE: usize = 50;
|
pub const DC_STR_SELFTALK_SUBTITLE: usize = 50;
|
||||||
const DC_STR_CANNOT_LOGIN: usize = 60;
|
pub const DC_STR_CANNOT_LOGIN: usize = 60;
|
||||||
const DC_STR_SERVER_RESPONSE: usize = 61;
|
pub const DC_STR_SERVER_RESPONSE: usize = 61;
|
||||||
const DC_STR_MSGACTIONBYUSER: usize = 62;
|
pub const DC_STR_MSGACTIONBYUSER: usize = 62;
|
||||||
const DC_STR_MSGACTIONBYME: usize = 63;
|
pub const DC_STR_MSGACTIONBYME: usize = 63;
|
||||||
const DC_STR_MSGLOCATIONENABLED: usize = 64;
|
pub const DC_STR_MSGLOCATIONENABLED: usize = 64;
|
||||||
const DC_STR_MSGLOCATIONDISABLED: usize = 65;
|
pub const DC_STR_MSGLOCATIONDISABLED: usize = 65;
|
||||||
const DC_STR_LOCATION: usize = 66;
|
pub const DC_STR_LOCATION: usize = 66;
|
||||||
const DC_STR_COUNT: usize = 66;
|
pub const DC_STR_COUNT: usize = 66;
|
||||||
|
|
||||||
pub const DC_JOB_DELETE_MSG_ON_IMAP: i32 = 110;
|
pub const DC_JOB_DELETE_MSG_ON_IMAP: i32 = 110;
|
||||||
|
|
||||||
|
|||||||
1090
src/contact.rs
1090
src/contact.rs
File diff suppressed because it is too large
Load Diff
1322
src/context.rs
1322
src/context.rs
File diff suppressed because it is too large
Load Diff
589
src/dc_array.rs
589
src/dc_array.rs
@@ -1,162 +1,495 @@
|
|||||||
use crate::location::Location;
|
use crate::context::*;
|
||||||
|
use crate::dc_tools::*;
|
||||||
|
use crate::types::*;
|
||||||
|
use crate::x::*;
|
||||||
|
|
||||||
/* * the structure behind dc_array_t */
|
/* * the structure behind dc_array_t */
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Copy, Clone)]
|
||||||
#[allow(non_camel_case_types)]
|
#[repr(C)]
|
||||||
pub enum dc_array_t {
|
pub struct dc_array_t {
|
||||||
Locations(Vec<Location>),
|
pub magic: uint32_t,
|
||||||
Uint(Vec<u32>),
|
pub allocated: size_t,
|
||||||
|
pub count: size_t,
|
||||||
|
pub type_0: libc::c_int,
|
||||||
|
pub array: *mut uintptr_t,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl dc_array_t {
|
/**
|
||||||
pub fn new(capacity: usize) -> Self {
|
* @class dc_array_t
|
||||||
dc_array_t::Uint(Vec::with_capacity(capacity))
|
*
|
||||||
|
* An object containing a simple array.
|
||||||
|
* This object is used in several places where functions need to return an array.
|
||||||
|
* The items of the array are typically IDs.
|
||||||
|
* To free an array object, use dc_array_unref().
|
||||||
|
*/
|
||||||
|
pub unsafe fn dc_array_unref(mut array: *mut dc_array_t) {
|
||||||
|
if array.is_null() || (*array).magic != 0xa11aai32 as libc::c_uint {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
if (*array).type_0 == 1i32 {
|
||||||
/// Constructs a new, empty `dc_array_t` holding locations with specified `capacity`.
|
dc_array_free_ptr(array);
|
||||||
pub fn new_locations(capacity: usize) -> Self {
|
|
||||||
dc_array_t::Locations(Vec::with_capacity(capacity))
|
|
||||||
}
|
}
|
||||||
|
free((*array).array as *mut libc::c_void);
|
||||||
|
(*array).magic = 0i32 as uint32_t;
|
||||||
|
free(array as *mut libc::c_void);
|
||||||
|
}
|
||||||
|
|
||||||
pub fn add_id(&mut self, item: u32) {
|
pub unsafe fn dc_array_free_ptr(array: *mut dc_array_t) {
|
||||||
if let Self::Uint(array) = self {
|
if array.is_null() || (*array).magic != 0xa11aai32 as libc::c_uint {
|
||||||
array.push(item);
|
return;
|
||||||
} else {
|
}
|
||||||
panic!("Attempt to add id to array of other type");
|
let mut i: size_t = 0i32 as size_t;
|
||||||
|
while i < (*array).count {
|
||||||
|
if (*array).type_0 == 1i32 {
|
||||||
|
free(
|
||||||
|
(*(*(*array).array.offset(i as isize) as *mut _dc_location)).marker
|
||||||
|
as *mut libc::c_void,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
free(*(*array).array.offset(i as isize) as *mut libc::c_void);
|
||||||
|
*(*array).array.offset(i as isize) = 0i32 as uintptr_t;
|
||||||
|
i = i.wrapping_add(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub unsafe fn dc_array_add_uint(mut array: *mut dc_array_t, item: uintptr_t) {
|
||||||
|
if array.is_null() || (*array).magic != 0xa11aai32 as libc::c_uint {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (*array).count == (*array).allocated {
|
||||||
|
let newsize = (*array).allocated.wrapping_mul(2).wrapping_add(10);
|
||||||
|
(*array).array = realloc(
|
||||||
|
(*array).array as *mut libc::c_void,
|
||||||
|
(newsize).wrapping_mul(::std::mem::size_of::<uintptr_t>()),
|
||||||
|
) as *mut uintptr_t;
|
||||||
|
assert!(!(*array).array.is_null());
|
||||||
|
(*array).allocated = newsize as size_t
|
||||||
|
}
|
||||||
|
*(*array).array.offset((*array).count as isize) = item;
|
||||||
|
(*array).count = (*array).count.wrapping_add(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub unsafe fn dc_array_add_id(array: *mut dc_array_t, item: uint32_t) {
|
||||||
|
dc_array_add_uint(array, item as uintptr_t);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub unsafe fn dc_array_add_ptr(array: *mut dc_array_t, item: *mut libc::c_void) {
|
||||||
|
dc_array_add_uint(array, item as uintptr_t);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub unsafe fn dc_array_get_cnt(array: *const dc_array_t) -> size_t {
|
||||||
|
if array.is_null() || (*array).magic != 0xa11aai32 as libc::c_uint {
|
||||||
|
return 0i32 as size_t;
|
||||||
|
}
|
||||||
|
(*array).count
|
||||||
|
}
|
||||||
|
|
||||||
|
pub unsafe fn dc_array_get_uint(array: *const dc_array_t, index: size_t) -> uintptr_t {
|
||||||
|
if array.is_null() || (*array).magic != 0xa11aai32 as libc::c_uint || index >= (*array).count {
|
||||||
|
return 0i32 as uintptr_t;
|
||||||
|
}
|
||||||
|
*(*array).array.offset(index as isize)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub unsafe fn dc_array_get_id(array: *const dc_array_t, index: size_t) -> uint32_t {
|
||||||
|
if array.is_null() || (*array).magic != 0xa11aai32 as libc::c_uint || index >= (*array).count {
|
||||||
|
return 0i32 as uint32_t;
|
||||||
|
}
|
||||||
|
if (*array).type_0 == 1i32 {
|
||||||
|
return (*(*(*array).array.offset(index as isize) as *mut _dc_location)).location_id;
|
||||||
|
}
|
||||||
|
*(*array).array.offset(index as isize) as uint32_t
|
||||||
|
}
|
||||||
|
|
||||||
|
pub unsafe fn dc_array_get_ptr(array: *const dc_array_t, index: size_t) -> *mut libc::c_void {
|
||||||
|
if array.is_null() || (*array).magic != 0xa11aai32 as libc::c_uint || index >= (*array).count {
|
||||||
|
return 0 as *mut libc::c_void;
|
||||||
|
}
|
||||||
|
*(*array).array.offset(index as isize) as *mut libc::c_void
|
||||||
|
}
|
||||||
|
|
||||||
|
pub unsafe fn dc_array_get_latitude(array: *const dc_array_t, index: size_t) -> libc::c_double {
|
||||||
|
if array.is_null()
|
||||||
|
|| (*array).magic != 0xa11aai32 as libc::c_uint
|
||||||
|
|| index >= (*array).count
|
||||||
|
|| (*array).type_0 != 1i32
|
||||||
|
|| *(*array).array.offset(index as isize) == 0
|
||||||
|
{
|
||||||
|
return 0i32 as libc::c_double;
|
||||||
|
}
|
||||||
|
(*(*(*array).array.offset(index as isize) as *mut _dc_location)).latitude
|
||||||
|
}
|
||||||
|
|
||||||
|
pub unsafe fn dc_array_get_longitude(array: *const dc_array_t, index: size_t) -> libc::c_double {
|
||||||
|
if array.is_null()
|
||||||
|
|| (*array).magic != 0xa11aai32 as libc::c_uint
|
||||||
|
|| index >= (*array).count
|
||||||
|
|| (*array).type_0 != 1i32
|
||||||
|
|| *(*array).array.offset(index as isize) == 0
|
||||||
|
{
|
||||||
|
return 0i32 as libc::c_double;
|
||||||
|
}
|
||||||
|
(*(*(*array).array.offset(index as isize) as *mut _dc_location)).longitude
|
||||||
|
}
|
||||||
|
|
||||||
|
pub unsafe fn dc_array_get_accuracy(array: *const dc_array_t, index: size_t) -> libc::c_double {
|
||||||
|
if array.is_null()
|
||||||
|
|| (*array).magic != 0xa11aai32 as libc::c_uint
|
||||||
|
|| index >= (*array).count
|
||||||
|
|| (*array).type_0 != 1i32
|
||||||
|
|| *(*array).array.offset(index as isize) == 0
|
||||||
|
{
|
||||||
|
return 0i32 as libc::c_double;
|
||||||
|
}
|
||||||
|
(*(*(*array).array.offset(index as isize) as *mut _dc_location)).accuracy
|
||||||
|
}
|
||||||
|
|
||||||
|
pub unsafe fn dc_array_get_timestamp(array: *const dc_array_t, index: size_t) -> i64 {
|
||||||
|
if array.is_null()
|
||||||
|
|| (*array).magic != 0xa11aai32 as libc::c_uint
|
||||||
|
|| index >= (*array).count
|
||||||
|
|| (*array).type_0 != 1i32
|
||||||
|
|| *(*array).array.offset(index as isize) == 0
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
(*(*(*array).array.offset(index as isize) as *mut _dc_location)).timestamp
|
||||||
|
}
|
||||||
|
|
||||||
|
pub unsafe fn dc_array_get_chat_id(array: *const dc_array_t, index: size_t) -> uint32_t {
|
||||||
|
if array.is_null()
|
||||||
|
|| (*array).magic != 0xa11aai32 as libc::c_uint
|
||||||
|
|| index >= (*array).count
|
||||||
|
|| (*array).type_0 != 1i32
|
||||||
|
|| *(*array).array.offset(index as isize) == 0
|
||||||
|
{
|
||||||
|
return 0i32 as uint32_t;
|
||||||
|
}
|
||||||
|
(*(*(*array).array.offset(index as isize) as *mut _dc_location)).chat_id
|
||||||
|
}
|
||||||
|
|
||||||
|
pub unsafe fn dc_array_get_contact_id(array: *const dc_array_t, index: size_t) -> uint32_t {
|
||||||
|
if array.is_null()
|
||||||
|
|| (*array).magic != 0xa11aai32 as libc::c_uint
|
||||||
|
|| index >= (*array).count
|
||||||
|
|| (*array).type_0 != 1i32
|
||||||
|
|| *(*array).array.offset(index as isize) == 0
|
||||||
|
{
|
||||||
|
return 0i32 as uint32_t;
|
||||||
|
}
|
||||||
|
(*(*(*array).array.offset(index as isize) as *mut _dc_location)).contact_id
|
||||||
|
}
|
||||||
|
|
||||||
|
pub unsafe fn dc_array_get_msg_id(array: *const dc_array_t, index: size_t) -> uint32_t {
|
||||||
|
if array.is_null()
|
||||||
|
|| (*array).magic != 0xa11aai32 as libc::c_uint
|
||||||
|
|| index >= (*array).count
|
||||||
|
|| (*array).type_0 != 1i32
|
||||||
|
|| *(*array).array.offset(index as isize) == 0
|
||||||
|
{
|
||||||
|
return 0i32 as uint32_t;
|
||||||
|
}
|
||||||
|
(*(*(*array).array.offset(index as isize) as *mut _dc_location)).msg_id
|
||||||
|
}
|
||||||
|
|
||||||
|
pub unsafe fn dc_array_get_marker(array: *const dc_array_t, index: size_t) -> *mut libc::c_char {
|
||||||
|
if array.is_null()
|
||||||
|
|| (*array).magic != 0xa11aai32 as libc::c_uint
|
||||||
|
|| index >= (*array).count
|
||||||
|
|| (*array).type_0 != 1i32
|
||||||
|
|| *(*array).array.offset(index as isize) == 0
|
||||||
|
{
|
||||||
|
return 0 as *mut libc::c_char;
|
||||||
|
}
|
||||||
|
dc_strdup_keep_null((*(*(*array).array.offset(index as isize) as *mut _dc_location)).marker)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the independent-state of the location at the given index.
|
||||||
|
* Independent locations do not belong to the track of the user.
|
||||||
|
*
|
||||||
|
* @memberof dc_array_t
|
||||||
|
* @param array The array object.
|
||||||
|
* @param index Index of the item. Must be between 0 and dc_array_get_cnt()-1.
|
||||||
|
* @return 0=Location belongs to the track of the user,
|
||||||
|
* 1=Location was reported independently.
|
||||||
|
*/
|
||||||
|
pub unsafe fn dc_array_is_independent(array: *const dc_array_t, index: size_t) -> libc::c_int {
|
||||||
|
if array.is_null()
|
||||||
|
|| (*array).magic != 0xa11aai32 as libc::c_uint
|
||||||
|
|| index >= (*array).count
|
||||||
|
|| (*array).type_0 != 1i32
|
||||||
|
|| *(*array).array.offset(index as isize) == 0
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_location(&mut self, location: Location) {
|
(*(*(*array).array.offset(index as isize) as *mut _dc_location)).independent as libc::c_int
|
||||||
if let Self::Locations(array) = self {
|
}
|
||||||
array.push(location)
|
|
||||||
} else {
|
|
||||||
panic!("Attempt to add a location to array of other type");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_id(&self, index: usize) -> u32 {
|
pub unsafe fn dc_array_search_id(
|
||||||
match self {
|
array: *const dc_array_t,
|
||||||
Self::Locations(array) => array[index].location_id,
|
needle: uint32_t,
|
||||||
Self::Uint(array) => array[index] as u32,
|
ret_index: *mut size_t,
|
||||||
}
|
) -> bool {
|
||||||
|
if array.is_null() || (*array).magic != 0xa11aai32 as libc::c_uint {
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
let data: *mut uintptr_t = (*array).array;
|
||||||
pub fn get_location(&self, index: usize) -> &Location {
|
let mut i: size_t = 0;
|
||||||
if let Self::Locations(array) = self {
|
let cnt: size_t = (*array).count;
|
||||||
&array[index]
|
while i < cnt {
|
||||||
} else {
|
if *data.offset(i as isize) == needle as size_t {
|
||||||
panic!("Not an array of locations")
|
if !ret_index.is_null() {
|
||||||
}
|
*ret_index = i
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_empty(&self) -> bool {
|
|
||||||
match self {
|
|
||||||
Self::Locations(array) => array.is_empty(),
|
|
||||||
Self::Uint(array) => array.is_empty(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the number of elements in the array.
|
|
||||||
pub fn len(&self) -> usize {
|
|
||||||
match self {
|
|
||||||
Self::Locations(array) => array.len(),
|
|
||||||
Self::Uint(array) => array.len(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn clear(&mut self) {
|
|
||||||
match self {
|
|
||||||
Self::Locations(array) => array.clear(),
|
|
||||||
Self::Uint(array) => array.clear(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn search_id(&self, needle: u32) -> Option<usize> {
|
|
||||||
if let Self::Uint(array) = self {
|
|
||||||
for (i, &u) in array.iter().enumerate() {
|
|
||||||
if u == needle {
|
|
||||||
return Some(i);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
None
|
return true;
|
||||||
} else {
|
|
||||||
panic!("Attempt to search for id in array of other type");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn sort_ids(&mut self) {
|
|
||||||
if let dc_array_t::Uint(v) = self {
|
|
||||||
v.sort();
|
|
||||||
} else {
|
|
||||||
panic!("Attempt to sort array of something other than uints");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn as_ptr(&self) -> *const u32 {
|
|
||||||
if let dc_array_t::Uint(v) = self {
|
|
||||||
v.as_ptr()
|
|
||||||
} else {
|
|
||||||
panic!("Attempt to convert array of something other than uints to raw");
|
|
||||||
}
|
}
|
||||||
|
i = i.wrapping_add(1)
|
||||||
}
|
}
|
||||||
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Vec<u32>> for dc_array_t {
|
pub unsafe fn dc_array_get_raw(array: *const dc_array_t) -> *const uintptr_t {
|
||||||
fn from(array: Vec<u32>) -> Self {
|
if array.is_null() || (*array).magic != 0xa11aai32 as libc::c_uint {
|
||||||
dc_array_t::Uint(array)
|
return 0 as *const uintptr_t;
|
||||||
}
|
}
|
||||||
|
(*array).array
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Vec<Location>> for dc_array_t {
|
pub unsafe fn dc_array_new(initsize: size_t) -> *mut dc_array_t {
|
||||||
fn from(array: Vec<Location>) -> Self {
|
dc_array_new_typed(0, initsize)
|
||||||
dc_array_t::Locations(array)
|
}
|
||||||
|
|
||||||
|
pub unsafe fn dc_array_new_typed(type_0: libc::c_int, initsize: size_t) -> *mut dc_array_t {
|
||||||
|
let mut array: *mut dc_array_t;
|
||||||
|
array = calloc(1, ::std::mem::size_of::<dc_array_t>()) as *mut dc_array_t;
|
||||||
|
assert!(!array.is_null());
|
||||||
|
|
||||||
|
(*array).magic = 0xa11aai32 as uint32_t;
|
||||||
|
(*array).count = 0i32 as size_t;
|
||||||
|
(*array).allocated = if initsize < 1 { 1 } else { initsize };
|
||||||
|
(*array).type_0 = type_0;
|
||||||
|
(*array).array = malloc(
|
||||||
|
(*array)
|
||||||
|
.allocated
|
||||||
|
.wrapping_mul(::std::mem::size_of::<uintptr_t>()),
|
||||||
|
) as *mut uintptr_t;
|
||||||
|
if (*array).array.is_null() {
|
||||||
|
exit(48i32);
|
||||||
}
|
}
|
||||||
|
array
|
||||||
|
}
|
||||||
|
|
||||||
|
pub unsafe fn dc_array_empty(mut array: *mut dc_array_t) {
|
||||||
|
if array.is_null() || (*array).magic != 0xa11aai32 as libc::c_uint {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
(*array).count = 0i32 as size_t;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub unsafe fn dc_array_duplicate(array: *const dc_array_t) -> *mut dc_array_t {
|
||||||
|
let mut ret: *mut dc_array_t;
|
||||||
|
if array.is_null() || (*array).magic != 0xa11aai32 as libc::c_uint {
|
||||||
|
return 0 as *mut dc_array_t;
|
||||||
|
}
|
||||||
|
ret = dc_array_new((*array).allocated);
|
||||||
|
(*ret).count = (*array).count;
|
||||||
|
memcpy(
|
||||||
|
(*ret).array as *mut libc::c_void,
|
||||||
|
(*array).array as *const libc::c_void,
|
||||||
|
(*array)
|
||||||
|
.count
|
||||||
|
.wrapping_mul(::std::mem::size_of::<uintptr_t>()),
|
||||||
|
);
|
||||||
|
ret
|
||||||
|
}
|
||||||
|
|
||||||
|
pub unsafe fn dc_array_sort_ids(array: *mut dc_array_t) {
|
||||||
|
if array.is_null() || (*array).magic != 0xa11aai32 as libc::c_uint || (*array).count <= 1 {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
qsort(
|
||||||
|
(*array).array as *mut libc::c_void,
|
||||||
|
(*array).count,
|
||||||
|
::std::mem::size_of::<uintptr_t>(),
|
||||||
|
Some(cmp_intptr_t),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe extern "C" fn cmp_intptr_t(p1: *const libc::c_void, p2: *const libc::c_void) -> libc::c_int {
|
||||||
|
let v1: uintptr_t = *(p1 as *mut uintptr_t);
|
||||||
|
let v2: uintptr_t = *(p2 as *mut uintptr_t);
|
||||||
|
return if v1 < v2 {
|
||||||
|
-1i32
|
||||||
|
} else if v1 > v2 {
|
||||||
|
1i32
|
||||||
|
} else {
|
||||||
|
0i32
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub unsafe fn dc_array_sort_strings(array: *mut dc_array_t) {
|
||||||
|
if array.is_null() || (*array).magic != 0xa11aai32 as libc::c_uint || (*array).count <= 1 {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
qsort(
|
||||||
|
(*array).array as *mut libc::c_void,
|
||||||
|
(*array).count,
|
||||||
|
::std::mem::size_of::<*mut libc::c_char>(),
|
||||||
|
Some(cmp_strings_t),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe extern "C" fn cmp_strings_t(
|
||||||
|
p1: *const libc::c_void,
|
||||||
|
p2: *const libc::c_void,
|
||||||
|
) -> libc::c_int {
|
||||||
|
let v1: *const libc::c_char = *(p1 as *mut *const libc::c_char);
|
||||||
|
let v2: *const libc::c_char = *(p2 as *mut *const libc::c_char);
|
||||||
|
|
||||||
|
strcmp(v1, v2)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub unsafe fn dc_array_get_string(
|
||||||
|
array: *const dc_array_t,
|
||||||
|
sep: *const libc::c_char,
|
||||||
|
) -> *mut libc::c_char {
|
||||||
|
if array.is_null() || (*array).magic != 0xa11aai32 as libc::c_uint || sep.is_null() {
|
||||||
|
return dc_strdup(b"\x00" as *const u8 as *const libc::c_char);
|
||||||
|
}
|
||||||
|
let cnt = (*array).count as usize;
|
||||||
|
let slice = std::slice::from_raw_parts((*array).array, cnt);
|
||||||
|
let sep = as_str(sep);
|
||||||
|
|
||||||
|
let res = slice
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.fold(String::with_capacity(2 * cnt), |mut res, (i, n)| {
|
||||||
|
if i == 0 {
|
||||||
|
res += &n.to_string();
|
||||||
|
} else {
|
||||||
|
res += sep;
|
||||||
|
res += &n.to_string();
|
||||||
|
}
|
||||||
|
res
|
||||||
|
});
|
||||||
|
strdup(to_cstring(res).as_ptr())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// return comma-separated value-string from integer array
|
||||||
|
pub unsafe fn dc_arr_to_string(arr: *const uint32_t, cnt: libc::c_int) -> *mut libc::c_char {
|
||||||
|
if arr.is_null() || cnt == 0 {
|
||||||
|
return dc_strdup(b"\x00" as *const u8 as *const libc::c_char);
|
||||||
|
}
|
||||||
|
|
||||||
|
let slice = std::slice::from_raw_parts(arr, cnt as usize);
|
||||||
|
let res = slice.iter().enumerate().fold(
|
||||||
|
String::with_capacity(2 * cnt as usize),
|
||||||
|
|mut res, (i, n)| {
|
||||||
|
if i == 0 {
|
||||||
|
res += &n.to_string();
|
||||||
|
} else {
|
||||||
|
res += ",";
|
||||||
|
res += &n.to_string();
|
||||||
|
}
|
||||||
|
res
|
||||||
|
},
|
||||||
|
);
|
||||||
|
strdup(to_cstring(res).as_ptr())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use std::ffi::CStr;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_dc_array() {
|
fn test_dc_array() {
|
||||||
let mut arr = dc_array_t::new(7);
|
unsafe {
|
||||||
assert!(arr.is_empty());
|
let arr = dc_array_new(7 as size_t);
|
||||||
|
assert_eq!(dc_array_get_cnt(arr), 0);
|
||||||
|
|
||||||
for i in 0..1000 {
|
let mut i: libc::c_int = 0;
|
||||||
arr.add_id(i + 2);
|
while i < 1000 {
|
||||||
|
dc_array_add_id(arr, (i + 2) as uint32_t);
|
||||||
|
i += 1
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_eq!(dc_array_get_cnt(arr), 1000);
|
||||||
|
|
||||||
|
i = 0;
|
||||||
|
|
||||||
|
while i < 1000i32 {
|
||||||
|
assert_eq!(
|
||||||
|
dc_array_get_id(arr, i as size_t),
|
||||||
|
(i + 1i32 * 2i32) as libc::c_uint
|
||||||
|
);
|
||||||
|
i += 1
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_eq!(dc_array_get_id(arr, -1i32 as size_t), 0);
|
||||||
|
assert_eq!(dc_array_get_id(arr, 1000 as size_t), 0);
|
||||||
|
assert_eq!(dc_array_get_id(arr, 1001 as size_t), 0);
|
||||||
|
|
||||||
|
dc_array_empty(arr);
|
||||||
|
|
||||||
|
assert_eq!(dc_array_get_cnt(arr), 0);
|
||||||
|
|
||||||
|
dc_array_add_id(arr, 13 as uint32_t);
|
||||||
|
dc_array_add_id(arr, 7 as uint32_t);
|
||||||
|
dc_array_add_id(arr, 666 as uint32_t);
|
||||||
|
dc_array_add_id(arr, 0 as uint32_t);
|
||||||
|
dc_array_add_id(arr, 5000 as uint32_t);
|
||||||
|
|
||||||
|
dc_array_sort_ids(arr);
|
||||||
|
|
||||||
|
assert_eq!(dc_array_get_id(arr, 0 as size_t), 0);
|
||||||
|
assert_eq!(dc_array_get_id(arr, 1 as size_t), 7);
|
||||||
|
assert_eq!(dc_array_get_id(arr, 2 as size_t), 13);
|
||||||
|
assert_eq!(dc_array_get_id(arr, 3 as size_t), 666);
|
||||||
|
|
||||||
|
let str = dc_array_get_string(arr, b"-\x00" as *const u8 as *const libc::c_char);
|
||||||
|
assert_eq!(
|
||||||
|
CStr::from_ptr(str as *const libc::c_char).to_str().unwrap(),
|
||||||
|
"0-7-13-666-5000"
|
||||||
|
);
|
||||||
|
free(str as *mut libc::c_void);
|
||||||
|
|
||||||
|
dc_array_empty(arr);
|
||||||
|
|
||||||
|
dc_array_add_ptr(
|
||||||
|
arr,
|
||||||
|
b"XX\x00" as *const u8 as *const libc::c_char as *mut libc::c_void,
|
||||||
|
);
|
||||||
|
dc_array_add_ptr(
|
||||||
|
arr,
|
||||||
|
b"item1\x00" as *const u8 as *const libc::c_char as *mut libc::c_void,
|
||||||
|
);
|
||||||
|
dc_array_add_ptr(
|
||||||
|
arr,
|
||||||
|
b"bbb\x00" as *const u8 as *const libc::c_char as *mut libc::c_void,
|
||||||
|
);
|
||||||
|
dc_array_add_ptr(
|
||||||
|
arr,
|
||||||
|
b"aaa\x00" as *const u8 as *const libc::c_char as *mut libc::c_void,
|
||||||
|
);
|
||||||
|
dc_array_sort_strings(arr);
|
||||||
|
|
||||||
|
let str = dc_array_get_ptr(arr, 0 as size_t) as *mut libc::c_char;
|
||||||
|
assert_eq!(CStr::from_ptr(str).to_str().unwrap(), "XX");
|
||||||
|
|
||||||
|
let str = dc_array_get_ptr(arr, 1 as size_t) as *mut libc::c_char;
|
||||||
|
assert_eq!(CStr::from_ptr(str).to_str().unwrap(), "aaa");
|
||||||
|
|
||||||
|
let str = dc_array_get_ptr(arr, 2 as size_t) as *mut libc::c_char;
|
||||||
|
assert_eq!(CStr::from_ptr(str).to_str().unwrap(), "bbb");
|
||||||
|
|
||||||
|
let str = dc_array_get_ptr(arr, 3 as size_t) as *mut libc::c_char;
|
||||||
|
assert_eq!(CStr::from_ptr(str).to_str().unwrap(), "item1");
|
||||||
|
|
||||||
|
dc_array_unref(arr);
|
||||||
}
|
}
|
||||||
|
|
||||||
assert_eq!(arr.len(), 1000);
|
|
||||||
|
|
||||||
for i in 0..1000 {
|
|
||||||
assert_eq!(arr.get_id(i), (i + 2) as u32);
|
|
||||||
}
|
|
||||||
|
|
||||||
arr.clear();
|
|
||||||
|
|
||||||
assert!(arr.is_empty());
|
|
||||||
|
|
||||||
arr.add_id(13);
|
|
||||||
arr.add_id(7);
|
|
||||||
arr.add_id(666);
|
|
||||||
arr.add_id(0);
|
|
||||||
arr.add_id(5000);
|
|
||||||
|
|
||||||
arr.sort_ids();
|
|
||||||
|
|
||||||
assert_eq!(arr.get_id(0), 0);
|
|
||||||
assert_eq!(arr.get_id(1), 7);
|
|
||||||
assert_eq!(arr.get_id(2), 13);
|
|
||||||
assert_eq!(arr.get_id(3), 666);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
#[should_panic]
|
|
||||||
fn test_dc_array_out_of_bounds() {
|
|
||||||
let mut arr = dc_array_t::new(7);
|
|
||||||
for i in 0..1000 {
|
|
||||||
arr.add_id(i + 2);
|
|
||||||
}
|
|
||||||
arr.get_id(1000);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
2424
src/dc_chat.rs
Normal file
2424
src/dc_chat.rs
Normal file
File diff suppressed because it is too large
Load Diff
368
src/dc_chatlist.rs
Normal file
368
src/dc_chatlist.rs
Normal file
@@ -0,0 +1,368 @@
|
|||||||
|
use crate::context::*;
|
||||||
|
use crate::dc_array::*;
|
||||||
|
use crate::dc_chat::*;
|
||||||
|
use crate::dc_contact::*;
|
||||||
|
use crate::dc_lot::*;
|
||||||
|
use crate::dc_msg::*;
|
||||||
|
use crate::dc_sqlite3::*;
|
||||||
|
use crate::dc_stock::*;
|
||||||
|
use crate::dc_tools::*;
|
||||||
|
use crate::types::*;
|
||||||
|
use crate::x::*;
|
||||||
|
|
||||||
|
/* * the structure behind dc_chatlist_t */
|
||||||
|
#[derive(Copy, Clone)]
|
||||||
|
#[repr(C)]
|
||||||
|
pub struct dc_chatlist_t<'a> {
|
||||||
|
pub magic: uint32_t,
|
||||||
|
pub context: &'a Context,
|
||||||
|
pub cnt: size_t,
|
||||||
|
pub chatNlastmsg_ids: *mut dc_array_t,
|
||||||
|
}
|
||||||
|
|
||||||
|
// handle chatlists
|
||||||
|
pub unsafe fn dc_get_chatlist<'a>(
|
||||||
|
context: &'a Context,
|
||||||
|
listflags: libc::c_int,
|
||||||
|
query_str: *const libc::c_char,
|
||||||
|
query_id: uint32_t,
|
||||||
|
) -> *mut dc_chatlist_t<'a> {
|
||||||
|
let mut success: libc::c_int = 0i32;
|
||||||
|
let obj: *mut dc_chatlist_t = dc_chatlist_new(context);
|
||||||
|
|
||||||
|
if !(0 == dc_chatlist_load_from_db(obj, listflags, query_str, query_id)) {
|
||||||
|
success = 1i32
|
||||||
|
}
|
||||||
|
|
||||||
|
if 0 != success {
|
||||||
|
return obj;
|
||||||
|
} else {
|
||||||
|
dc_chatlist_unref(obj);
|
||||||
|
return 0 as *mut dc_chatlist_t;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @class dc_chatlist_t
|
||||||
|
*
|
||||||
|
* An object representing a single chatlist in memory.
|
||||||
|
* Chatlist objects contain chat IDs
|
||||||
|
* and, if possible, message IDs belonging to them.
|
||||||
|
* The chatlist object is not updated;
|
||||||
|
* if you want an update, you have to recreate the object.
|
||||||
|
*
|
||||||
|
* For a **typical chat overview**,
|
||||||
|
* the idea is to get the list of all chats via dc_get_chatlist()
|
||||||
|
* without any listflags (see below)
|
||||||
|
* and to implement a "virtual list" or so
|
||||||
|
* (the count of chats is known by dc_chatlist_get_cnt()).
|
||||||
|
*
|
||||||
|
* Only for the items that are in view
|
||||||
|
* (the list may have several hundreds chats),
|
||||||
|
* the UI should call dc_chatlist_get_summary() then.
|
||||||
|
* dc_chatlist_get_summary() provides all elements needed for painting the item.
|
||||||
|
*
|
||||||
|
* On a click of such an item,
|
||||||
|
* the UI should change to the chat view
|
||||||
|
* and get all messages from this view via dc_get_chat_msgs().
|
||||||
|
* Again, a "virtual list" is created
|
||||||
|
* (the count of messages is known)
|
||||||
|
* and for each messages that is scrolled into view, dc_get_msg() is called then.
|
||||||
|
*
|
||||||
|
* Why no listflags?
|
||||||
|
* Without listflags, dc_get_chatlist() adds the deaddrop
|
||||||
|
* and the archive "link" automatically as needed.
|
||||||
|
* The UI can just render these items differently then.
|
||||||
|
* Although the deaddrop link is currently always the first entry
|
||||||
|
* and only present on new messages,
|
||||||
|
* there is the rough idea that it can be optionally always present
|
||||||
|
* and sorted into the list by date.
|
||||||
|
* Rendering the deaddrop in the described way
|
||||||
|
* would not add extra work in the UI then.
|
||||||
|
*/
|
||||||
|
pub unsafe fn dc_chatlist_new(context: &Context) -> *mut dc_chatlist_t {
|
||||||
|
let mut chatlist: *mut dc_chatlist_t;
|
||||||
|
chatlist = calloc(1, ::std::mem::size_of::<dc_chatlist_t>()) as *mut dc_chatlist_t;
|
||||||
|
assert!(!chatlist.is_null());
|
||||||
|
|
||||||
|
(*chatlist).magic = 0xc4a71157u32;
|
||||||
|
(*chatlist).context = context;
|
||||||
|
(*chatlist).chatNlastmsg_ids = dc_array_new(128i32 as size_t);
|
||||||
|
assert!(!(*chatlist).chatNlastmsg_ids.is_null());
|
||||||
|
chatlist
|
||||||
|
}
|
||||||
|
|
||||||
|
pub unsafe fn dc_chatlist_unref(mut chatlist: *mut dc_chatlist_t) {
|
||||||
|
if chatlist.is_null() || (*chatlist).magic != 0xc4a71157u32 {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
dc_chatlist_empty(chatlist);
|
||||||
|
dc_array_unref((*chatlist).chatNlastmsg_ids);
|
||||||
|
(*chatlist).magic = 0i32 as uint32_t;
|
||||||
|
free(chatlist as *mut libc::c_void);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub unsafe fn dc_chatlist_empty(mut chatlist: *mut dc_chatlist_t) {
|
||||||
|
if chatlist.is_null() || (*chatlist).magic != 0xc4a71157u32 {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
(*chatlist).cnt = 0i32 as size_t;
|
||||||
|
dc_array_empty((*chatlist).chatNlastmsg_ids);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load a chatlist from the database to the chatlist object.
|
||||||
|
*
|
||||||
|
* @private @memberof dc_chatlist_t
|
||||||
|
*/
|
||||||
|
// TODO should return bool /rtn
|
||||||
|
unsafe fn dc_chatlist_load_from_db(
|
||||||
|
mut chatlist: *mut dc_chatlist_t,
|
||||||
|
listflags: libc::c_int,
|
||||||
|
query__: *const libc::c_char,
|
||||||
|
query_contact_id: uint32_t,
|
||||||
|
) -> libc::c_int {
|
||||||
|
let current_block: u64;
|
||||||
|
//clock_t start = clock();
|
||||||
|
let mut success: libc::c_int = 0i32;
|
||||||
|
let mut add_archived_link_item: libc::c_int = 0i32;
|
||||||
|
let mut stmt: *mut sqlite3_stmt = 0 as *mut sqlite3_stmt;
|
||||||
|
let mut strLikeCmd: *mut libc::c_char = 0 as *mut libc::c_char;
|
||||||
|
let mut query: *mut libc::c_char = 0 as *mut libc::c_char;
|
||||||
|
if !(chatlist.is_null() || (*chatlist).magic != 0xc4a71157u32) {
|
||||||
|
dc_chatlist_empty(chatlist);
|
||||||
|
// select with left join and minimum:
|
||||||
|
// - the inner select must use `hidden` and _not_ `m.hidden`
|
||||||
|
// which would refer the outer select and take a lot of time
|
||||||
|
// - `GROUP BY` is needed several messages may have the same timestamp
|
||||||
|
// - the list starts with the newest chats
|
||||||
|
// nb: the query currently shows messages from blocked contacts in groups.
|
||||||
|
// however, for normal-groups, this is okay as the message is also returned by dc_get_chat_msgs()
|
||||||
|
// (otherwise it would be hard to follow conversations, wa and tg do the same)
|
||||||
|
// for the deaddrop, however, they should really be hidden, however, _currently_ the deaddrop is not
|
||||||
|
// shown at all permanent in the chatlist.
|
||||||
|
if 0 != query_contact_id {
|
||||||
|
stmt =
|
||||||
|
dc_sqlite3_prepare(
|
||||||
|
(*chatlist).context,
|
||||||
|
&(*chatlist).context.sql,
|
||||||
|
b"SELECT c.id, m.id FROM chats c LEFT JOIN msgs m ON c.id=m.chat_id AND m.timestamp=( SELECT MAX(timestamp) FROM msgs WHERE chat_id=c.id AND (hidden=0 OR (hidden=1 AND state=19))) WHERE c.id>9 AND c.blocked=0 AND c.id IN(SELECT chat_id FROM chats_contacts WHERE contact_id=?) GROUP BY c.id ORDER BY IFNULL(m.timestamp,0) DESC, m.id DESC;\x00"
|
||||||
|
as *const u8 as *const libc::c_char
|
||||||
|
);
|
||||||
|
sqlite3_bind_int(stmt, 1i32, query_contact_id as libc::c_int);
|
||||||
|
current_block = 3437258052017859086;
|
||||||
|
} else if 0 != listflags & 0x1i32 {
|
||||||
|
stmt =
|
||||||
|
dc_sqlite3_prepare(
|
||||||
|
(*chatlist).context,
|
||||||
|
&(*chatlist).context.sql,
|
||||||
|
b"SELECT c.id, m.id FROM chats c LEFT JOIN msgs m ON c.id=m.chat_id AND m.timestamp=( SELECT MAX(timestamp) FROM msgs WHERE chat_id=c.id AND (hidden=0 OR (hidden=1 AND state=19))) WHERE c.id>9 AND c.blocked=0 AND c.archived=1 GROUP BY c.id ORDER BY IFNULL(m.timestamp,0) DESC, m.id DESC;\x00"
|
||||||
|
as *const u8 as *const libc::c_char);
|
||||||
|
current_block = 3437258052017859086;
|
||||||
|
} else if query__.is_null() {
|
||||||
|
if 0 == listflags & 0x2i32 {
|
||||||
|
let last_deaddrop_fresh_msg_id: uint32_t =
|
||||||
|
get_last_deaddrop_fresh_msg((*chatlist).context);
|
||||||
|
if last_deaddrop_fresh_msg_id > 0i32 as libc::c_uint {
|
||||||
|
dc_array_add_id((*chatlist).chatNlastmsg_ids, 1i32 as uint32_t);
|
||||||
|
dc_array_add_id((*chatlist).chatNlastmsg_ids, last_deaddrop_fresh_msg_id);
|
||||||
|
}
|
||||||
|
add_archived_link_item = 1i32
|
||||||
|
}
|
||||||
|
stmt =
|
||||||
|
dc_sqlite3_prepare(
|
||||||
|
(*chatlist).context,
|
||||||
|
&(*chatlist).context.sql,
|
||||||
|
b"SELECT c.id, m.id FROM chats c LEFT JOIN msgs m ON c.id=m.chat_id AND m.timestamp=( SELECT MAX(timestamp) FROM msgs WHERE chat_id=c.id AND (hidden=0 OR (hidden=1 AND state=19))) WHERE c.id>9 AND c.blocked=0 AND c.archived=0 GROUP BY c.id ORDER BY IFNULL(m.timestamp,0) DESC, m.id DESC;\x00"
|
||||||
|
as *const u8 as *const libc::c_char);
|
||||||
|
current_block = 3437258052017859086;
|
||||||
|
} else {
|
||||||
|
query = dc_strdup(query__);
|
||||||
|
dc_trim(query);
|
||||||
|
if *query.offset(0isize) as libc::c_int == 0i32 {
|
||||||
|
success = 1i32;
|
||||||
|
current_block = 15179736777190528364;
|
||||||
|
} else {
|
||||||
|
strLikeCmd = dc_mprintf(b"%%%s%%\x00" as *const u8 as *const libc::c_char, query);
|
||||||
|
stmt =
|
||||||
|
dc_sqlite3_prepare(
|
||||||
|
(*chatlist).context,
|
||||||
|
&(*chatlist).context.sql,
|
||||||
|
b"SELECT c.id, m.id FROM chats c LEFT JOIN msgs m ON c.id=m.chat_id AND m.timestamp=( SELECT MAX(timestamp) FROM msgs WHERE chat_id=c.id AND (hidden=0 OR (hidden=1 AND state=19))) WHERE c.id>9 AND c.blocked=0 AND c.name LIKE ? GROUP BY c.id ORDER BY IFNULL(m.timestamp,0) DESC, m.id DESC;\x00"
|
||||||
|
as *const u8 as
|
||||||
|
*const libc::c_char);
|
||||||
|
sqlite3_bind_text(stmt, 1i32, strLikeCmd, -1i32, None);
|
||||||
|
current_block = 3437258052017859086;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
match current_block {
|
||||||
|
15179736777190528364 => {}
|
||||||
|
_ => {
|
||||||
|
while sqlite3_step(stmt) == 100i32 {
|
||||||
|
dc_array_add_id(
|
||||||
|
(*chatlist).chatNlastmsg_ids,
|
||||||
|
sqlite3_column_int(stmt, 0i32) as uint32_t,
|
||||||
|
);
|
||||||
|
dc_array_add_id(
|
||||||
|
(*chatlist).chatNlastmsg_ids,
|
||||||
|
sqlite3_column_int(stmt, 1i32) as uint32_t,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if 0 != add_archived_link_item && dc_get_archived_cnt((*chatlist).context) > 0i32 {
|
||||||
|
if dc_array_get_cnt((*chatlist).chatNlastmsg_ids) == 0
|
||||||
|
&& 0 != listflags & 0x4i32
|
||||||
|
{
|
||||||
|
dc_array_add_id((*chatlist).chatNlastmsg_ids, 7i32 as uint32_t);
|
||||||
|
dc_array_add_id((*chatlist).chatNlastmsg_ids, 0i32 as uint32_t);
|
||||||
|
}
|
||||||
|
dc_array_add_id((*chatlist).chatNlastmsg_ids, 6i32 as uint32_t);
|
||||||
|
dc_array_add_id((*chatlist).chatNlastmsg_ids, 0i32 as uint32_t);
|
||||||
|
}
|
||||||
|
(*chatlist).cnt = dc_array_get_cnt((*chatlist).chatNlastmsg_ids).wrapping_div(2);
|
||||||
|
success = 1i32
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sqlite3_finalize(stmt);
|
||||||
|
free(query as *mut libc::c_void);
|
||||||
|
free(strLikeCmd as *mut libc::c_void);
|
||||||
|
success
|
||||||
|
}
|
||||||
|
|
||||||
|
// Context functions to work with chatlist
|
||||||
|
pub unsafe fn dc_get_archived_cnt(context: &Context) -> libc::c_int {
|
||||||
|
let mut ret: libc::c_int = 0i32;
|
||||||
|
let stmt: *mut sqlite3_stmt = dc_sqlite3_prepare(
|
||||||
|
context,
|
||||||
|
&context.sql,
|
||||||
|
b"SELECT COUNT(*) FROM chats WHERE blocked=0 AND archived=1;\x00" as *const u8
|
||||||
|
as *const libc::c_char,
|
||||||
|
);
|
||||||
|
if sqlite3_step(stmt) == 100i32 {
|
||||||
|
ret = sqlite3_column_int(stmt, 0i32)
|
||||||
|
}
|
||||||
|
sqlite3_finalize(stmt);
|
||||||
|
ret
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe fn get_last_deaddrop_fresh_msg(context: &Context) -> uint32_t {
|
||||||
|
let mut ret: uint32_t = 0i32 as uint32_t;
|
||||||
|
let stmt: *mut sqlite3_stmt;
|
||||||
|
stmt =
|
||||||
|
dc_sqlite3_prepare(
|
||||||
|
context,
|
||||||
|
&context.sql,
|
||||||
|
b"SELECT m.id FROM msgs m LEFT JOIN chats c ON c.id=m.chat_id WHERE m.state=10 AND m.hidden=0 AND c.blocked=2 ORDER BY m.timestamp DESC, m.id DESC;\x00"
|
||||||
|
as *const u8 as *const libc::c_char);
|
||||||
|
/* we have an index over the state-column, this should be sufficient as there are typically only few fresh messages */
|
||||||
|
if !(sqlite3_step(stmt) != 100i32) {
|
||||||
|
ret = sqlite3_column_int(stmt, 0i32) as uint32_t
|
||||||
|
}
|
||||||
|
sqlite3_finalize(stmt);
|
||||||
|
ret
|
||||||
|
}
|
||||||
|
|
||||||
|
pub unsafe fn dc_chatlist_get_cnt(chatlist: *const dc_chatlist_t) -> size_t {
|
||||||
|
if chatlist.is_null() || (*chatlist).magic != 0xc4a71157u32 {
|
||||||
|
return 0i32 as size_t;
|
||||||
|
}
|
||||||
|
(*chatlist).cnt
|
||||||
|
}
|
||||||
|
|
||||||
|
pub unsafe fn dc_chatlist_get_chat_id(chatlist: *const dc_chatlist_t, index: size_t) -> uint32_t {
|
||||||
|
if chatlist.is_null()
|
||||||
|
|| (*chatlist).magic != 0xc4a71157u32
|
||||||
|
|| (*chatlist).chatNlastmsg_ids.is_null()
|
||||||
|
|| index >= (*chatlist).cnt
|
||||||
|
{
|
||||||
|
return 0i32 as uint32_t;
|
||||||
|
}
|
||||||
|
dc_array_get_id((*chatlist).chatNlastmsg_ids, index.wrapping_mul(2))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub unsafe fn dc_chatlist_get_msg_id(chatlist: *const dc_chatlist_t, index: size_t) -> uint32_t {
|
||||||
|
if chatlist.is_null()
|
||||||
|
|| (*chatlist).magic != 0xc4a71157u32
|
||||||
|
|| (*chatlist).chatNlastmsg_ids.is_null()
|
||||||
|
|| index >= (*chatlist).cnt
|
||||||
|
{
|
||||||
|
return 0i32 as uint32_t;
|
||||||
|
}
|
||||||
|
dc_array_get_id(
|
||||||
|
(*chatlist).chatNlastmsg_ids,
|
||||||
|
index.wrapping_mul(2).wrapping_add(1),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub unsafe fn dc_chatlist_get_summary<'a>(
|
||||||
|
chatlist: *const dc_chatlist_t<'a>,
|
||||||
|
index: size_t,
|
||||||
|
mut chat: *mut Chat<'a>,
|
||||||
|
) -> *mut dc_lot_t {
|
||||||
|
let current_block: u64;
|
||||||
|
/* The summary is created by the chat, not by the last message.
|
||||||
|
This is because we may want to display drafts here or stuff as
|
||||||
|
"is typing".
|
||||||
|
Also, sth. as "No messages" would not work if the summary comes from a
|
||||||
|
message. */
|
||||||
|
/* the function never returns NULL */
|
||||||
|
let mut ret: *mut dc_lot_t = dc_lot_new();
|
||||||
|
let lastmsg_id: uint32_t;
|
||||||
|
let mut lastmsg: *mut dc_msg_t = 0 as *mut dc_msg_t;
|
||||||
|
let mut lastcontact: *mut dc_contact_t = 0 as *mut dc_contact_t;
|
||||||
|
let mut chat_to_delete: *mut Chat = 0 as *mut Chat;
|
||||||
|
if chatlist.is_null() || (*chatlist).magic != 0xc4a71157u32 || index >= (*chatlist).cnt {
|
||||||
|
(*ret).text2 = dc_strdup(b"ErrBadChatlistIndex\x00" as *const u8 as *const libc::c_char)
|
||||||
|
} else {
|
||||||
|
lastmsg_id = dc_array_get_id(
|
||||||
|
(*chatlist).chatNlastmsg_ids,
|
||||||
|
index.wrapping_mul(2).wrapping_add(1),
|
||||||
|
);
|
||||||
|
if chat.is_null() {
|
||||||
|
chat = dc_chat_new((*chatlist).context);
|
||||||
|
chat_to_delete = chat;
|
||||||
|
if !dc_chat_load_from_db(
|
||||||
|
chat,
|
||||||
|
dc_array_get_id((*chatlist).chatNlastmsg_ids, index.wrapping_mul(2)),
|
||||||
|
) {
|
||||||
|
(*ret).text2 =
|
||||||
|
dc_strdup(b"ErrCannotReadChat\x00" as *const u8 as *const libc::c_char);
|
||||||
|
current_block = 3777403817673069519;
|
||||||
|
} else {
|
||||||
|
current_block = 7651349459974463963;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
current_block = 7651349459974463963;
|
||||||
|
}
|
||||||
|
match current_block {
|
||||||
|
3777403817673069519 => {}
|
||||||
|
_ => {
|
||||||
|
if 0 != lastmsg_id {
|
||||||
|
lastmsg = dc_msg_new_untyped((*chatlist).context);
|
||||||
|
dc_msg_load_from_db(lastmsg, (*chatlist).context, lastmsg_id);
|
||||||
|
if (*lastmsg).from_id != 1i32 as libc::c_uint
|
||||||
|
&& ((*chat).type_0 == 120i32 || (*chat).type_0 == 130i32)
|
||||||
|
{
|
||||||
|
lastcontact = dc_contact_new((*chatlist).context);
|
||||||
|
dc_contact_load_from_db(
|
||||||
|
lastcontact,
|
||||||
|
&(*chatlist).context.sql,
|
||||||
|
(*lastmsg).from_id,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (*chat).id == 6i32 as libc::c_uint {
|
||||||
|
(*ret).text2 = dc_strdup(0 as *const libc::c_char)
|
||||||
|
} else if lastmsg.is_null() || (*lastmsg).from_id == 0i32 as libc::c_uint {
|
||||||
|
(*ret).text2 = dc_stock_str((*chatlist).context, 1i32)
|
||||||
|
} else {
|
||||||
|
dc_lot_fill(ret, lastmsg, chat, lastcontact, (*chatlist).context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dc_msg_unref(lastmsg);
|
||||||
|
dc_contact_unref(lastcontact);
|
||||||
|
dc_chat_unref(chat_to_delete);
|
||||||
|
ret
|
||||||
|
}
|
||||||
1744
src/dc_configure.rs
Normal file
1744
src/dc_configure.rs
Normal file
File diff suppressed because it is too large
Load Diff
1288
src/dc_contact.rs
Normal file
1288
src/dc_contact.rs
Normal file
File diff suppressed because it is too large
Load Diff
169
src/dc_dehtml.rs
169
src/dc_dehtml.rs
@@ -1,6 +1,8 @@
|
|||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
use quick_xml;
|
|
||||||
use quick_xml::events::{BytesEnd, BytesStart, BytesText};
|
use crate::dc_saxparser::*;
|
||||||
|
use crate::dc_tools::*;
|
||||||
|
use crate::x::*;
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
static ref LINE_RE: regex::Regex = regex::Regex::new(r"(\r?\n)+").unwrap();
|
static ref LINE_RE: regex::Regex = regex::Regex::new(r"(\r?\n)+").unwrap();
|
||||||
@@ -9,7 +11,7 @@ lazy_static! {
|
|||||||
struct Dehtml {
|
struct Dehtml {
|
||||||
strbuilder: String,
|
strbuilder: String,
|
||||||
add_text: AddText,
|
add_text: AddText,
|
||||||
last_href: Option<String>,
|
last_href: *mut libc::c_char,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
@@ -21,88 +23,77 @@ enum AddText {
|
|||||||
|
|
||||||
// dc_dehtml() returns way too many lineends; however, an optimisation on this issue is not needed as
|
// dc_dehtml() returns way too many lineends; however, an optimisation on this issue is not needed as
|
||||||
// the lineends are typically remove in further processing by the caller
|
// the lineends are typically remove in further processing by the caller
|
||||||
pub fn dc_dehtml(buf_terminated: &str) -> String {
|
pub unsafe fn dc_dehtml(buf_terminated: *mut libc::c_char) -> *mut libc::c_char {
|
||||||
let buf_terminated = buf_terminated.trim();
|
dc_trim(buf_terminated);
|
||||||
|
if *buf_terminated.offset(0isize) as libc::c_int == 0i32 {
|
||||||
if buf_terminated.is_empty() {
|
return dc_strdup(b"\x00" as *const u8 as *const libc::c_char);
|
||||||
return "".into();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut dehtml = Dehtml {
|
let mut dehtml = Dehtml {
|
||||||
strbuilder: String::with_capacity(buf_terminated.len()),
|
strbuilder: String::with_capacity(strlen(buf_terminated)),
|
||||||
add_text: AddText::YesRemoveLineEnds,
|
add_text: AddText::YesRemoveLineEnds,
|
||||||
last_href: None,
|
last_href: 0 as *mut libc::c_char,
|
||||||
};
|
};
|
||||||
|
let mut saxparser = dc_saxparser_t {
|
||||||
|
starttag_cb: None,
|
||||||
|
endtag_cb: None,
|
||||||
|
text_cb: None,
|
||||||
|
userdata: 0 as *mut libc::c_void,
|
||||||
|
};
|
||||||
|
dc_saxparser_init(
|
||||||
|
&mut saxparser,
|
||||||
|
&mut dehtml as *mut Dehtml as *mut libc::c_void,
|
||||||
|
);
|
||||||
|
dc_saxparser_set_tag_handler(
|
||||||
|
&mut saxparser,
|
||||||
|
Some(dehtml_starttag_cb),
|
||||||
|
Some(dehtml_endtag_cb),
|
||||||
|
);
|
||||||
|
dc_saxparser_set_text_handler(&mut saxparser, Some(dehtml_text_cb));
|
||||||
|
dc_saxparser_parse(&mut saxparser, buf_terminated);
|
||||||
|
free(dehtml.last_href as *mut libc::c_void);
|
||||||
|
|
||||||
let mut reader = quick_xml::Reader::from_str(buf_terminated);
|
strdup(to_cstring(dehtml.strbuilder).as_ptr())
|
||||||
|
|
||||||
let mut buf = Vec::new();
|
|
||||||
|
|
||||||
loop {
|
|
||||||
match reader.read_event(&mut buf) {
|
|
||||||
Ok(quick_xml::events::Event::Start(ref e)) => {
|
|
||||||
dehtml_starttag_cb(e, &mut dehtml, &reader)
|
|
||||||
}
|
|
||||||
Ok(quick_xml::events::Event::End(ref e)) => dehtml_endtag_cb(e, &mut dehtml),
|
|
||||||
Ok(quick_xml::events::Event::Text(ref e)) => dehtml_text_cb(e, &mut dehtml),
|
|
||||||
Ok(quick_xml::events::Event::CData(ref e)) => dehtml_cdata_cb(e, &mut dehtml),
|
|
||||||
Err(e) => {
|
|
||||||
eprintln!(
|
|
||||||
"Parse html error: Error at position {}: {:?}",
|
|
||||||
reader.buffer_position(),
|
|
||||||
e
|
|
||||||
);
|
|
||||||
}
|
|
||||||
Ok(quick_xml::events::Event::Eof) => break,
|
|
||||||
_ => (),
|
|
||||||
}
|
|
||||||
buf.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
dehtml.strbuilder
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn dehtml_text_cb(event: &BytesText, dehtml: &mut Dehtml) {
|
unsafe fn dehtml_text_cb(
|
||||||
|
userdata: *mut libc::c_void,
|
||||||
|
text: *const libc::c_char,
|
||||||
|
_len: libc::c_int,
|
||||||
|
) {
|
||||||
|
let dehtml = &mut *(userdata as *mut Dehtml);
|
||||||
|
|
||||||
if dehtml.add_text == AddText::YesPreserveLineEnds
|
if dehtml.add_text == AddText::YesPreserveLineEnds
|
||||||
|| dehtml.add_text == AddText::YesRemoveLineEnds
|
|| dehtml.add_text == AddText::YesRemoveLineEnds
|
||||||
{
|
{
|
||||||
let last_added = escaper::decode_html_buf_sloppy(event.escaped()).unwrap_or_default();
|
let last_added = std::ffi::CStr::from_ptr(text).to_string_lossy();
|
||||||
|
|
||||||
if dehtml.add_text == AddText::YesRemoveLineEnds {
|
if dehtml.add_text == AddText::YesRemoveLineEnds {
|
||||||
dehtml.strbuilder += LINE_RE.replace_all(&last_added, "\r").as_ref();
|
dehtml.strbuilder += LINE_RE.replace_all(last_added.as_ref(), "\r").as_ref();
|
||||||
} else {
|
} else {
|
||||||
dehtml.strbuilder += &last_added;
|
dehtml.strbuilder += last_added.as_ref();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn dehtml_cdata_cb(event: &BytesText, dehtml: &mut Dehtml) {
|
unsafe fn dehtml_endtag_cb(userdata: *mut libc::c_void, tag: *const libc::c_char) {
|
||||||
if dehtml.add_text == AddText::YesPreserveLineEnds
|
let mut dehtml = &mut *(userdata as *mut Dehtml);
|
||||||
|| dehtml.add_text == AddText::YesRemoveLineEnds
|
let tag = std::ffi::CStr::from_ptr(tag).to_string_lossy();
|
||||||
{
|
|
||||||
let last_added = escaper::decode_html_buf_sloppy(event.escaped()).unwrap_or_default();
|
|
||||||
|
|
||||||
if dehtml.add_text == AddText::YesRemoveLineEnds {
|
match tag.as_ref() {
|
||||||
dehtml.strbuilder += LINE_RE.replace_all(&last_added, "\r").as_ref();
|
|
||||||
} else {
|
|
||||||
dehtml.strbuilder += &last_added;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn dehtml_endtag_cb(event: &BytesEnd, dehtml: &mut Dehtml) {
|
|
||||||
let tag = String::from_utf8_lossy(event.name()).trim().to_lowercase();
|
|
||||||
|
|
||||||
match tag.as_str() {
|
|
||||||
"p" | "div" | "table" | "td" | "style" | "script" | "title" | "pre" => {
|
"p" | "div" | "table" | "td" | "style" | "script" | "title" | "pre" => {
|
||||||
dehtml.strbuilder += "\n\n";
|
dehtml.strbuilder += "\n\n";
|
||||||
dehtml.add_text = AddText::YesRemoveLineEnds;
|
dehtml.add_text = AddText::YesRemoveLineEnds;
|
||||||
}
|
}
|
||||||
"a" => {
|
"a" => {
|
||||||
if let Some(ref last_href) = dehtml.last_href.take() {
|
if !dehtml.last_href.is_null() {
|
||||||
dehtml.strbuilder += "](";
|
dehtml.strbuilder += "](";
|
||||||
dehtml.strbuilder += last_href;
|
dehtml.strbuilder += std::ffi::CStr::from_ptr((*dehtml).last_href)
|
||||||
|
.to_string_lossy()
|
||||||
|
.as_ref();
|
||||||
dehtml.strbuilder += ")";
|
dehtml.strbuilder += ")";
|
||||||
|
free(dehtml.last_href as *mut libc::c_void);
|
||||||
|
dehtml.last_href = 0 as *mut libc::c_char;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"b" | "strong" => {
|
"b" | "strong" => {
|
||||||
@@ -115,14 +106,15 @@ fn dehtml_endtag_cb(event: &BytesEnd, dehtml: &mut Dehtml) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn dehtml_starttag_cb<B: std::io::BufRead>(
|
unsafe fn dehtml_starttag_cb(
|
||||||
event: &BytesStart,
|
userdata: *mut libc::c_void,
|
||||||
dehtml: &mut Dehtml,
|
tag: *const libc::c_char,
|
||||||
reader: &quick_xml::Reader<B>,
|
attr: *mut *mut libc::c_char,
|
||||||
) {
|
) {
|
||||||
let tag = String::from_utf8_lossy(event.name()).trim().to_lowercase();
|
let mut dehtml = &mut *(userdata as *mut Dehtml);
|
||||||
|
let tag = std::ffi::CStr::from_ptr(tag).to_string_lossy();
|
||||||
|
|
||||||
match tag.as_str() {
|
match tag.as_ref() {
|
||||||
"p" | "div" | "table" | "td" => {
|
"p" | "div" | "table" | "td" => {
|
||||||
dehtml.strbuilder += "\n\n";
|
dehtml.strbuilder += "\n\n";
|
||||||
dehtml.add_text = AddText::YesRemoveLineEnds;
|
dehtml.add_text = AddText::YesRemoveLineEnds;
|
||||||
@@ -139,21 +131,13 @@ fn dehtml_starttag_cb<B: std::io::BufRead>(
|
|||||||
dehtml.add_text = AddText::YesPreserveLineEnds;
|
dehtml.add_text = AddText::YesPreserveLineEnds;
|
||||||
}
|
}
|
||||||
"a" => {
|
"a" => {
|
||||||
if let Some(href) = event.html_attributes().find(|attr| {
|
free(dehtml.last_href as *mut libc::c_void);
|
||||||
attr.as_ref()
|
dehtml.last_href = dc_strdup_keep_null(dc_attr_find(
|
||||||
.map(|a| String::from_utf8_lossy(a.key).trim().to_lowercase() == "href")
|
attr,
|
||||||
.unwrap_or_default()
|
b"href\x00" as *const u8 as *const libc::c_char,
|
||||||
}) {
|
));
|
||||||
let href = href
|
if !dehtml.last_href.is_null() {
|
||||||
.unwrap()
|
dehtml.strbuilder += "[";
|
||||||
.unescape_and_decode_value(reader)
|
|
||||||
.unwrap_or_default()
|
|
||||||
.to_lowercase();
|
|
||||||
|
|
||||||
if !href.is_empty() {
|
|
||||||
dehtml.last_href = Some(href);
|
|
||||||
dehtml.strbuilder += "[";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"b" | "strong" => {
|
"b" | "strong" => {
|
||||||
@@ -165,28 +149,3 @@ fn dehtml_starttag_cb<B: std::io::BufRead>(
|
|||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_dc_dehtml() {
|
|
||||||
let cases = vec",
|
|
||||||
),
|
|
||||||
("<img href='/foo.png'>", ""),
|
|
||||||
("<b> bar </b>", "* bar *"),
|
|
||||||
("<b> bar <i> foo", "* bar _ foo"),
|
|
||||||
("& bar", "& bar"),
|
|
||||||
// Note missing '
|
|
||||||
("<a href='/foo.png>Hi</a> ", ""),
|
|
||||||
("", ""),
|
|
||||||
];
|
|
||||||
for (input, output) in cases {
|
|
||||||
assert_eq!(dc_dehtml(input), output);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
1127
src/dc_e2ee.rs
Normal file
1127
src/dc_e2ee.rs
Normal file
File diff suppressed because it is too large
Load Diff
1801
src/dc_imex.rs
1801
src/dc_imex.rs
File diff suppressed because it is too large
Load Diff
1400
src/dc_job.rs
Normal file
1400
src/dc_job.rs
Normal file
File diff suppressed because it is too large
Load Diff
220
src/dc_jobthread.rs
Normal file
220
src/dc_jobthread.rs
Normal file
@@ -0,0 +1,220 @@
|
|||||||
|
use std::sync::{Arc, Condvar, Mutex};
|
||||||
|
|
||||||
|
use crate::context::Context;
|
||||||
|
use crate::dc_configure::*;
|
||||||
|
use crate::dc_sqlite3::*;
|
||||||
|
use crate::imap::Imap;
|
||||||
|
use crate::types::*;
|
||||||
|
use crate::x::*;
|
||||||
|
use std::ffi::CString;
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
pub struct dc_jobthread_t {
|
||||||
|
pub name: &'static str,
|
||||||
|
pub folder_config_name: &'static str,
|
||||||
|
pub imap: Imap,
|
||||||
|
pub state: Arc<(Mutex<JobState>, Condvar)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn dc_jobthread_init(
|
||||||
|
name: &'static str,
|
||||||
|
folder_config_name: &'static str,
|
||||||
|
imap: Imap,
|
||||||
|
) -> dc_jobthread_t {
|
||||||
|
dc_jobthread_t {
|
||||||
|
name,
|
||||||
|
folder_config_name,
|
||||||
|
imap,
|
||||||
|
state: Arc::new((Mutex::new(Default::default()), Condvar::new())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
pub struct JobState {
|
||||||
|
idle: bool,
|
||||||
|
jobs_needed: i32,
|
||||||
|
suspended: i32,
|
||||||
|
using_handle: i32,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub unsafe fn dc_jobthread_suspend(
|
||||||
|
context: &Context,
|
||||||
|
jobthread: &dc_jobthread_t,
|
||||||
|
suspend: libc::c_int,
|
||||||
|
) {
|
||||||
|
if 0 != suspend {
|
||||||
|
info!(context, 0, "Suspending {}-thread.", jobthread.name,);
|
||||||
|
{
|
||||||
|
jobthread.state.0.lock().unwrap().suspended = 1;
|
||||||
|
}
|
||||||
|
dc_jobthread_interrupt_idle(context, jobthread);
|
||||||
|
loop {
|
||||||
|
let using_handle = jobthread.state.0.lock().unwrap().using_handle;
|
||||||
|
if using_handle == 0 {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
std::thread::sleep(std::time::Duration::from_micros(300 * 1000));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
info!(context, 0, "Unsuspending {}-thread.", jobthread.name);
|
||||||
|
|
||||||
|
let &(ref lock, ref cvar) = &*jobthread.state.clone();
|
||||||
|
let mut state = lock.lock().unwrap();
|
||||||
|
|
||||||
|
state.suspended = 0;
|
||||||
|
state.idle = true;
|
||||||
|
cvar.notify_one();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub unsafe fn dc_jobthread_interrupt_idle(context: &Context, jobthread: &dc_jobthread_t) {
|
||||||
|
{
|
||||||
|
jobthread.state.0.lock().unwrap().jobs_needed = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
info!(context, 0, "Interrupting {}-IDLE...", jobthread.name);
|
||||||
|
|
||||||
|
jobthread.imap.interrupt_idle();
|
||||||
|
|
||||||
|
let &(ref lock, ref cvar) = &*jobthread.state.clone();
|
||||||
|
let mut state = lock.lock().unwrap();
|
||||||
|
|
||||||
|
state.idle = true;
|
||||||
|
cvar.notify_one();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub unsafe fn dc_jobthread_fetch(
|
||||||
|
context: &Context,
|
||||||
|
jobthread: &mut dc_jobthread_t,
|
||||||
|
use_network: libc::c_int,
|
||||||
|
) {
|
||||||
|
let start;
|
||||||
|
|
||||||
|
{
|
||||||
|
let &(ref lock, _) = &*jobthread.state.clone();
|
||||||
|
let mut state = lock.lock().unwrap();
|
||||||
|
|
||||||
|
if 0 != state.suspended {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
state.using_handle = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if 0 != use_network {
|
||||||
|
start = clock();
|
||||||
|
if !(0 == connect_to_imap(context, jobthread)) {
|
||||||
|
info!(context, 0, "{}-fetch started...", jobthread.name);
|
||||||
|
jobthread.imap.fetch(context);
|
||||||
|
|
||||||
|
if jobthread.imap.should_reconnect() {
|
||||||
|
info!(
|
||||||
|
context,
|
||||||
|
0, "{}-fetch aborted, starting over...", jobthread.name,
|
||||||
|
);
|
||||||
|
jobthread.imap.fetch(context);
|
||||||
|
}
|
||||||
|
info!(
|
||||||
|
context,
|
||||||
|
0,
|
||||||
|
"{}-fetch done in {:.3} ms.",
|
||||||
|
jobthread.name,
|
||||||
|
clock().wrapping_sub(start) as f64 / 1000.0,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
jobthread.state.0.lock().unwrap().using_handle = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ******************************************************************************
|
||||||
|
* the typical fetch, idle, interrupt-idle
|
||||||
|
******************************************************************************/
|
||||||
|
|
||||||
|
unsafe fn connect_to_imap(context: &Context, jobthread: &dc_jobthread_t) -> libc::c_int {
|
||||||
|
let mut ret_connected: libc::c_int;
|
||||||
|
let mut mvbox_name: *mut libc::c_char = 0 as *mut libc::c_char;
|
||||||
|
|
||||||
|
if jobthread.imap.is_connected() {
|
||||||
|
ret_connected = 1;
|
||||||
|
} else {
|
||||||
|
ret_connected = dc_connect_to_configured_imap(context, &jobthread.imap);
|
||||||
|
if !(0 == ret_connected) {
|
||||||
|
if dc_sqlite3_get_config_int(
|
||||||
|
context,
|
||||||
|
&context.sql,
|
||||||
|
b"folders_configured\x00" as *const u8 as *const libc::c_char,
|
||||||
|
0,
|
||||||
|
) < 3
|
||||||
|
{
|
||||||
|
jobthread.imap.configure_folders(context, 0x1);
|
||||||
|
}
|
||||||
|
mvbox_name = dc_sqlite3_get_config(
|
||||||
|
context,
|
||||||
|
&context.sql,
|
||||||
|
CString::new(&jobthread.folder_config_name[..])
|
||||||
|
.unwrap()
|
||||||
|
.as_ptr(),
|
||||||
|
0 as *const libc::c_char,
|
||||||
|
);
|
||||||
|
if mvbox_name.is_null() {
|
||||||
|
jobthread.imap.disconnect(context);
|
||||||
|
ret_connected = 0;
|
||||||
|
} else {
|
||||||
|
jobthread.imap.set_watch_folder(mvbox_name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
free(mvbox_name as *mut libc::c_void);
|
||||||
|
|
||||||
|
ret_connected
|
||||||
|
}
|
||||||
|
|
||||||
|
pub unsafe fn dc_jobthread_idle(
|
||||||
|
context: &Context,
|
||||||
|
jobthread: &dc_jobthread_t,
|
||||||
|
use_network: libc::c_int,
|
||||||
|
) {
|
||||||
|
{
|
||||||
|
let &(ref lock, ref cvar) = &*jobthread.state.clone();
|
||||||
|
let mut state = lock.lock().unwrap();
|
||||||
|
|
||||||
|
if 0 != state.jobs_needed {
|
||||||
|
info!(
|
||||||
|
context,
|
||||||
|
0,
|
||||||
|
"{}-IDLE will not be started as it was interrupted while not ideling.",
|
||||||
|
jobthread.name,
|
||||||
|
);
|
||||||
|
state.jobs_needed = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if 0 != state.suspended {
|
||||||
|
while !state.idle {
|
||||||
|
state = cvar.wait(state).unwrap();
|
||||||
|
}
|
||||||
|
state.idle = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
state.using_handle = 1;
|
||||||
|
|
||||||
|
if 0 == use_network {
|
||||||
|
state.using_handle = 0;
|
||||||
|
|
||||||
|
while !state.idle {
|
||||||
|
state = cvar.wait(state).unwrap();
|
||||||
|
}
|
||||||
|
state.idle = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
connect_to_imap(context, jobthread);
|
||||||
|
info!(context, 0, "{}-IDLE started...", jobthread.name,);
|
||||||
|
jobthread.imap.idle(context);
|
||||||
|
info!(context, 0, "{}-IDLE ended.", jobthread.name);
|
||||||
|
|
||||||
|
jobthread.state.0.lock().unwrap().using_handle = 0;
|
||||||
|
}
|
||||||
835
src/dc_location.rs
Normal file
835
src/dc_location.rs
Normal file
@@ -0,0 +1,835 @@
|
|||||||
|
use crate::constants::Event;
|
||||||
|
use crate::context::*;
|
||||||
|
use crate::dc_array::*;
|
||||||
|
use crate::dc_chat::*;
|
||||||
|
use crate::dc_job::*;
|
||||||
|
use crate::dc_log::*;
|
||||||
|
use crate::dc_msg::*;
|
||||||
|
use crate::dc_param::*;
|
||||||
|
use crate::dc_saxparser::*;
|
||||||
|
use crate::dc_sqlite3::*;
|
||||||
|
use crate::dc_stock::*;
|
||||||
|
use crate::dc_tools::*;
|
||||||
|
use crate::types::*;
|
||||||
|
use crate::x::*;
|
||||||
|
|
||||||
|
// location handling
|
||||||
|
#[derive(Copy, Clone)]
|
||||||
|
#[repr(C)]
|
||||||
|
pub struct dc_location_t {
|
||||||
|
pub location_id: uint32_t,
|
||||||
|
pub latitude: libc::c_double,
|
||||||
|
pub longitude: libc::c_double,
|
||||||
|
pub accuracy: libc::c_double,
|
||||||
|
pub timestamp: i64,
|
||||||
|
pub contact_id: uint32_t,
|
||||||
|
pub msg_id: uint32_t,
|
||||||
|
pub chat_id: uint32_t,
|
||||||
|
pub marker: *mut libc::c_char,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone)]
|
||||||
|
#[repr(C)]
|
||||||
|
pub struct dc_kml_t {
|
||||||
|
pub addr: *mut libc::c_char,
|
||||||
|
pub locations: *mut dc_array_t,
|
||||||
|
pub tag: libc::c_int,
|
||||||
|
pub curr: dc_location_t,
|
||||||
|
}
|
||||||
|
|
||||||
|
// location streaming
|
||||||
|
pub unsafe fn dc_send_locations_to_chat(
|
||||||
|
context: &Context,
|
||||||
|
chat_id: uint32_t,
|
||||||
|
seconds: libc::c_int,
|
||||||
|
) {
|
||||||
|
let mut stmt: *mut sqlite3_stmt = 0 as *mut sqlite3_stmt;
|
||||||
|
let now = time();
|
||||||
|
let mut msg: *mut dc_msg_t = 0 as *mut dc_msg_t;
|
||||||
|
let mut stock_str: *mut libc::c_char = 0 as *mut libc::c_char;
|
||||||
|
let is_sending_locations_before: libc::c_int;
|
||||||
|
if !(seconds < 0i32 || chat_id <= 9i32 as libc::c_uint) {
|
||||||
|
is_sending_locations_before = dc_is_sending_locations_to_chat(context, chat_id);
|
||||||
|
stmt =
|
||||||
|
dc_sqlite3_prepare(
|
||||||
|
context,
|
||||||
|
&context.sql,
|
||||||
|
b"UPDATE chats SET locations_send_begin=?, locations_send_until=? WHERE id=?\x00"
|
||||||
|
as *const u8 as *const libc::c_char);
|
||||||
|
sqlite3_bind_int64(
|
||||||
|
stmt,
|
||||||
|
1i32,
|
||||||
|
(if 0 != seconds { now } else { 0 }) as sqlite3_int64,
|
||||||
|
);
|
||||||
|
sqlite3_bind_int64(
|
||||||
|
stmt,
|
||||||
|
2i32,
|
||||||
|
(if 0 != seconds {
|
||||||
|
now + seconds as i64
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
}) as sqlite3_int64,
|
||||||
|
);
|
||||||
|
sqlite3_bind_int(stmt, 3i32, chat_id as libc::c_int);
|
||||||
|
sqlite3_step(stmt);
|
||||||
|
if 0 != seconds && 0 == is_sending_locations_before {
|
||||||
|
msg = dc_msg_new(context, 10i32);
|
||||||
|
(*msg).text = dc_stock_system_msg(
|
||||||
|
context,
|
||||||
|
64i32,
|
||||||
|
0 as *const libc::c_char,
|
||||||
|
0 as *const libc::c_char,
|
||||||
|
0i32 as uint32_t,
|
||||||
|
);
|
||||||
|
dc_param_set_int((*msg).param, 'S' as i32, 8i32);
|
||||||
|
dc_send_msg(context, chat_id, msg);
|
||||||
|
} else if 0 == seconds && 0 != is_sending_locations_before {
|
||||||
|
stock_str = dc_stock_system_msg(
|
||||||
|
context,
|
||||||
|
65i32,
|
||||||
|
0 as *const libc::c_char,
|
||||||
|
0 as *const libc::c_char,
|
||||||
|
0i32 as uint32_t,
|
||||||
|
);
|
||||||
|
dc_add_device_msg(context, chat_id, stock_str);
|
||||||
|
}
|
||||||
|
context.call_cb(
|
||||||
|
Event::CHAT_MODIFIED,
|
||||||
|
chat_id as uintptr_t,
|
||||||
|
0i32 as uintptr_t,
|
||||||
|
);
|
||||||
|
if 0 != seconds {
|
||||||
|
schedule_MAYBE_SEND_LOCATIONS(context, 0i32);
|
||||||
|
dc_job_add(
|
||||||
|
context,
|
||||||
|
5007i32,
|
||||||
|
chat_id as libc::c_int,
|
||||||
|
0 as *const libc::c_char,
|
||||||
|
seconds + 1i32,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
free(stock_str as *mut libc::c_void);
|
||||||
|
dc_msg_unref(msg);
|
||||||
|
sqlite3_finalize(stmt);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
* job to send locations out to all chats that want them
|
||||||
|
******************************************************************************/
|
||||||
|
unsafe fn schedule_MAYBE_SEND_LOCATIONS(context: &Context, flags: libc::c_int) {
|
||||||
|
if 0 != flags & 0x1i32 || 0 == dc_job_action_exists(context, 5005i32) {
|
||||||
|
dc_job_add(context, 5005i32, 0i32, 0 as *const libc::c_char, 60i32);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub unsafe fn dc_is_sending_locations_to_chat(context: &Context, chat_id: uint32_t) -> libc::c_int {
|
||||||
|
let mut is_sending_locations: libc::c_int = 0i32;
|
||||||
|
let stmt: *mut sqlite3_stmt;
|
||||||
|
|
||||||
|
stmt = dc_sqlite3_prepare(
|
||||||
|
context,
|
||||||
|
&context.sql,
|
||||||
|
b"SELECT id FROM chats WHERE (? OR id=?) AND locations_send_until>?;\x00" as *const u8
|
||||||
|
as *const libc::c_char,
|
||||||
|
);
|
||||||
|
sqlite3_bind_int(
|
||||||
|
stmt,
|
||||||
|
1i32,
|
||||||
|
if chat_id == 0i32 as libc::c_uint {
|
||||||
|
1i32
|
||||||
|
} else {
|
||||||
|
0i32
|
||||||
|
},
|
||||||
|
);
|
||||||
|
sqlite3_bind_int(stmt, 2i32, chat_id as libc::c_int);
|
||||||
|
sqlite3_bind_int64(stmt, 3i32, time() as sqlite3_int64);
|
||||||
|
if !(sqlite3_step(stmt) != 100i32) {
|
||||||
|
is_sending_locations = 1i32
|
||||||
|
}
|
||||||
|
|
||||||
|
sqlite3_finalize(stmt);
|
||||||
|
|
||||||
|
is_sending_locations
|
||||||
|
}
|
||||||
|
|
||||||
|
pub unsafe fn dc_set_location(
|
||||||
|
context: &Context,
|
||||||
|
latitude: libc::c_double,
|
||||||
|
longitude: libc::c_double,
|
||||||
|
accuracy: libc::c_double,
|
||||||
|
) -> libc::c_int {
|
||||||
|
let mut stmt_chats: *mut sqlite3_stmt = 0 as *mut sqlite3_stmt;
|
||||||
|
let mut stmt_insert: *mut sqlite3_stmt = 0 as *mut sqlite3_stmt;
|
||||||
|
let mut continue_streaming: libc::c_int = 0i32;
|
||||||
|
if latitude == 0.0f64 && longitude == 0.0f64 {
|
||||||
|
continue_streaming = 1i32
|
||||||
|
} else {
|
||||||
|
stmt_chats = dc_sqlite3_prepare(
|
||||||
|
context,
|
||||||
|
&context.sql,
|
||||||
|
b"SELECT id FROM chats WHERE locations_send_until>?;\x00" as *const u8
|
||||||
|
as *const libc::c_char,
|
||||||
|
);
|
||||||
|
sqlite3_bind_int64(stmt_chats, 1i32, time() as sqlite3_int64);
|
||||||
|
while sqlite3_step(stmt_chats) == 100i32 {
|
||||||
|
let chat_id: uint32_t = sqlite3_column_int(stmt_chats, 0i32) as uint32_t;
|
||||||
|
stmt_insert =
|
||||||
|
dc_sqlite3_prepare(
|
||||||
|
context,
|
||||||
|
&context.sql,
|
||||||
|
b"INSERT INTO locations (latitude, longitude, accuracy, timestamp, chat_id, from_id) VALUES (?,?,?,?,?,?);\x00"
|
||||||
|
as *const u8 as *const libc::c_char);
|
||||||
|
sqlite3_bind_double(stmt_insert, 1i32, latitude);
|
||||||
|
sqlite3_bind_double(stmt_insert, 2i32, longitude);
|
||||||
|
sqlite3_bind_double(stmt_insert, 3i32, accuracy);
|
||||||
|
sqlite3_bind_int64(stmt_insert, 4i32, time() as sqlite3_int64);
|
||||||
|
sqlite3_bind_int(stmt_insert, 5i32, chat_id as libc::c_int);
|
||||||
|
sqlite3_bind_int(stmt_insert, 6i32, 1i32);
|
||||||
|
sqlite3_step(stmt_insert);
|
||||||
|
continue_streaming = 1i32
|
||||||
|
}
|
||||||
|
if 0 != continue_streaming {
|
||||||
|
context.call_cb(
|
||||||
|
Event::LOCATION_CHANGED,
|
||||||
|
1i32 as uintptr_t,
|
||||||
|
0i32 as uintptr_t,
|
||||||
|
);
|
||||||
|
schedule_MAYBE_SEND_LOCATIONS(context, 0i32);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sqlite3_finalize(stmt_chats);
|
||||||
|
sqlite3_finalize(stmt_insert);
|
||||||
|
|
||||||
|
continue_streaming
|
||||||
|
}
|
||||||
|
|
||||||
|
pub unsafe fn dc_get_locations(
|
||||||
|
context: &Context,
|
||||||
|
chat_id: uint32_t,
|
||||||
|
contact_id: uint32_t,
|
||||||
|
timestamp_from: i64,
|
||||||
|
mut timestamp_to: i64,
|
||||||
|
) -> *mut dc_array_t {
|
||||||
|
let ret: *mut dc_array_t = dc_array_new_typed(1i32, 500i32 as size_t);
|
||||||
|
let stmt: *mut sqlite3_stmt;
|
||||||
|
|
||||||
|
if timestamp_to == 0 {
|
||||||
|
timestamp_to = time() + 10;
|
||||||
|
}
|
||||||
|
stmt = dc_sqlite3_prepare(
|
||||||
|
context,
|
||||||
|
&context.sql,
|
||||||
|
b"SELECT l.id, l.latitude, l.longitude, l.accuracy, l.timestamp, l.independent, \
|
||||||
|
m.id, l.from_id, l.chat_id, m.txt \
|
||||||
|
FROM locations l LEFT JOIN msgs m ON l.id=m.location_id WHERE (? OR l.chat_id=?) \
|
||||||
|
AND (? OR l.from_id=?) \
|
||||||
|
AND (l.independent=1 OR (l.timestamp>=? AND l.timestamp<=?)) \
|
||||||
|
ORDER BY l.timestamp DESC, l.id DESC, m.id DESC;\x00" as *const u8
|
||||||
|
as *const libc::c_char,
|
||||||
|
);
|
||||||
|
sqlite3_bind_int(
|
||||||
|
stmt,
|
||||||
|
1i32,
|
||||||
|
if chat_id == 0i32 as libc::c_uint {
|
||||||
|
1i32
|
||||||
|
} else {
|
||||||
|
0i32
|
||||||
|
},
|
||||||
|
);
|
||||||
|
sqlite3_bind_int(stmt, 2i32, chat_id as libc::c_int);
|
||||||
|
sqlite3_bind_int(
|
||||||
|
stmt,
|
||||||
|
3i32,
|
||||||
|
if contact_id == 0i32 as libc::c_uint {
|
||||||
|
1i32
|
||||||
|
} else {
|
||||||
|
0i32
|
||||||
|
},
|
||||||
|
);
|
||||||
|
sqlite3_bind_int(stmt, 4i32, contact_id as libc::c_int);
|
||||||
|
sqlite3_bind_int(stmt, 5i32, timestamp_from as libc::c_int);
|
||||||
|
sqlite3_bind_int(stmt, 6i32, timestamp_to as libc::c_int);
|
||||||
|
while sqlite3_step(stmt) == 100i32 {
|
||||||
|
let mut loc: *mut _dc_location =
|
||||||
|
calloc(1, ::std::mem::size_of::<_dc_location>()) as *mut _dc_location;
|
||||||
|
if loc.is_null() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
(*loc).location_id = sqlite3_column_double(stmt, 0i32) as uint32_t;
|
||||||
|
(*loc).latitude = sqlite3_column_double(stmt, 1i32);
|
||||||
|
(*loc).longitude = sqlite3_column_double(stmt, 2i32);
|
||||||
|
(*loc).accuracy = sqlite3_column_double(stmt, 3i32);
|
||||||
|
(*loc).timestamp = sqlite3_column_int64(stmt, 4i32) as i64;
|
||||||
|
(*loc).independent = sqlite3_column_int(stmt, 5i32) as uint32_t;
|
||||||
|
(*loc).msg_id = sqlite3_column_int(stmt, 6i32) as uint32_t;
|
||||||
|
(*loc).contact_id = sqlite3_column_int(stmt, 7i32) as uint32_t;
|
||||||
|
(*loc).chat_id = sqlite3_column_int(stmt, 8i32) as uint32_t;
|
||||||
|
|
||||||
|
if 0 != (*loc).msg_id {
|
||||||
|
let txt: *const libc::c_char = sqlite3_column_text(stmt, 9i32) as *const libc::c_char;
|
||||||
|
if 0 != is_marker(txt) {
|
||||||
|
(*loc).marker = strdup(txt)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dc_array_add_ptr(ret, loc as *mut libc::c_void);
|
||||||
|
}
|
||||||
|
|
||||||
|
sqlite3_finalize(stmt);
|
||||||
|
|
||||||
|
ret
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO should be bool /rtn
|
||||||
|
unsafe fn is_marker(txt: *const libc::c_char) -> libc::c_int {
|
||||||
|
if !txt.is_null() {
|
||||||
|
let len: libc::c_int = dc_utf8_strlen(txt) as libc::c_int;
|
||||||
|
if len == 1i32 && *txt.offset(0isize) as libc::c_int != ' ' as i32 {
|
||||||
|
return 1i32;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
0
|
||||||
|
}
|
||||||
|
|
||||||
|
pub unsafe fn dc_delete_all_locations(context: &Context) {
|
||||||
|
let stmt: *mut sqlite3_stmt;
|
||||||
|
|
||||||
|
stmt = dc_sqlite3_prepare(
|
||||||
|
context,
|
||||||
|
&context.sql,
|
||||||
|
b"DELETE FROM locations;\x00" as *const u8 as *const libc::c_char,
|
||||||
|
);
|
||||||
|
sqlite3_step(stmt);
|
||||||
|
context.call_cb(
|
||||||
|
Event::LOCATION_CHANGED,
|
||||||
|
0i32 as uintptr_t,
|
||||||
|
0i32 as uintptr_t,
|
||||||
|
);
|
||||||
|
|
||||||
|
sqlite3_finalize(stmt);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub unsafe fn dc_get_location_kml(
|
||||||
|
context: &Context,
|
||||||
|
chat_id: uint32_t,
|
||||||
|
last_added_location_id: *mut uint32_t,
|
||||||
|
) -> *mut libc::c_char {
|
||||||
|
let mut success: libc::c_int = 0i32;
|
||||||
|
let mut stmt: *mut sqlite3_stmt;
|
||||||
|
let self_addr: *mut libc::c_char;
|
||||||
|
let now = time();
|
||||||
|
let locations_send_begin: i64;
|
||||||
|
let locations_send_until: i64;
|
||||||
|
let locations_last_sent: i64;
|
||||||
|
let mut location_count: libc::c_int = 0i32;
|
||||||
|
let mut ret = String::new();
|
||||||
|
|
||||||
|
self_addr = dc_sqlite3_get_config(
|
||||||
|
context,
|
||||||
|
&context.sql,
|
||||||
|
b"configured_addr\x00" as *const u8 as *const libc::c_char,
|
||||||
|
b"\x00" as *const u8 as *const libc::c_char,
|
||||||
|
);
|
||||||
|
stmt =
|
||||||
|
dc_sqlite3_prepare(
|
||||||
|
context,
|
||||||
|
&context.sql,
|
||||||
|
b"SELECT locations_send_begin, locations_send_until, locations_last_sent FROM chats WHERE id=?;\x00"
|
||||||
|
as *const u8 as *const libc::c_char);
|
||||||
|
sqlite3_bind_int(stmt, 1i32, chat_id as libc::c_int);
|
||||||
|
if !(sqlite3_step(stmt) != 100i32) {
|
||||||
|
locations_send_begin = sqlite3_column_int64(stmt, 0i32) as i64;
|
||||||
|
locations_send_until = sqlite3_column_int64(stmt, 1i32) as i64;
|
||||||
|
locations_last_sent = sqlite3_column_int64(stmt, 2i32) as i64;
|
||||||
|
sqlite3_finalize(stmt);
|
||||||
|
stmt = 0 as *mut sqlite3_stmt;
|
||||||
|
|
||||||
|
if !(locations_send_begin == 0 || now > locations_send_until) {
|
||||||
|
ret += &format!(
|
||||||
|
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<kml xmlns=\"http://www.opengis.net/kml/2.2\">\n<Document addr=\"{}\">\n",
|
||||||
|
to_string(self_addr),
|
||||||
|
);
|
||||||
|
stmt = dc_sqlite3_prepare(
|
||||||
|
context,
|
||||||
|
&context.sql,
|
||||||
|
b"SELECT id, latitude, longitude, accuracy, timestamp\
|
||||||
|
FROM locations WHERE from_id=? \
|
||||||
|
AND timestamp>=? \
|
||||||
|
AND (timestamp>=? OR timestamp=(SELECT MAX(timestamp) FROM locations WHERE from_id=?)) \
|
||||||
|
AND independent=0 \
|
||||||
|
GROUP BY timestamp \
|
||||||
|
ORDER BY timestamp;\x00" as *const u8
|
||||||
|
as *const libc::c_char,
|
||||||
|
);
|
||||||
|
|
||||||
|
sqlite3_bind_int(stmt, 1i32, 1i32);
|
||||||
|
sqlite3_bind_int64(stmt, 2i32, locations_send_begin as sqlite3_int64);
|
||||||
|
sqlite3_bind_int64(stmt, 3i32, locations_last_sent as sqlite3_int64);
|
||||||
|
sqlite3_bind_int(stmt, 4i32, 1i32);
|
||||||
|
while sqlite3_step(stmt) == 100i32 {
|
||||||
|
let location_id: uint32_t = sqlite3_column_int(stmt, 0i32) as uint32_t;
|
||||||
|
let latitude = sqlite3_column_double(stmt, 1i32);
|
||||||
|
let longitude = sqlite3_column_double(stmt, 2i32);
|
||||||
|
let accuracy = sqlite3_column_double(stmt, 3i32);
|
||||||
|
let timestamp = get_kml_timestamp(sqlite3_column_int64(stmt, 4i32) as i64);
|
||||||
|
ret += &format!(
|
||||||
|
"<Placemark><Timestamp><when>{}</when></Timestamp><Point><coordinates accuracy=\"{}\">{},{}</coordinates></Point></Placemark>\n\x00",
|
||||||
|
as_str(timestamp),
|
||||||
|
accuracy,
|
||||||
|
longitude,
|
||||||
|
latitude
|
||||||
|
);
|
||||||
|
location_count += 1;
|
||||||
|
if !last_added_location_id.is_null() {
|
||||||
|
*last_added_location_id = location_id
|
||||||
|
}
|
||||||
|
free(timestamp as *mut libc::c_void);
|
||||||
|
}
|
||||||
|
if !(location_count == 0) {
|
||||||
|
ret += "</Document>\n</kml>";
|
||||||
|
success = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sqlite3_finalize(stmt);
|
||||||
|
free(self_addr as *mut libc::c_void);
|
||||||
|
|
||||||
|
if 0 != success {
|
||||||
|
strdup(to_cstring(ret).as_ptr())
|
||||||
|
} else {
|
||||||
|
0 as *mut libc::c_char
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
* create kml-files
|
||||||
|
******************************************************************************/
|
||||||
|
unsafe fn get_kml_timestamp(utc: i64) -> *mut libc::c_char {
|
||||||
|
// Returns a string formatted as YYYY-MM-DDTHH:MM:SSZ. The trailing `Z` indicates UTC.
|
||||||
|
let res = chrono::NaiveDateTime::from_timestamp(utc, 0)
|
||||||
|
.format("%Y-%m-%dT%H:%M:%SZ")
|
||||||
|
.to_string();
|
||||||
|
strdup(to_cstring(res).as_ptr())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub unsafe fn dc_get_message_kml(
|
||||||
|
timestamp: i64,
|
||||||
|
latitude: libc::c_double,
|
||||||
|
longitude: libc::c_double,
|
||||||
|
) -> *mut libc::c_char {
|
||||||
|
let timestamp_str = get_kml_timestamp(timestamp);
|
||||||
|
let latitude_str = dc_ftoa(latitude);
|
||||||
|
let longitude_str = dc_ftoa(longitude);
|
||||||
|
|
||||||
|
let ret = dc_mprintf(
|
||||||
|
b"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\
|
||||||
|
<kml xmlns=\"http://www.opengis.net/kml/2.2\">\n\
|
||||||
|
<Document>\n\
|
||||||
|
<Placemark>\
|
||||||
|
<Timestamp><when>%s</when></Timestamp>\
|
||||||
|
<Point><coordinates>%s,%s</coordinates></Point>\
|
||||||
|
</Placemark>\n\
|
||||||
|
</Document>\n\
|
||||||
|
</kml>\x00" as *const u8 as *const libc::c_char,
|
||||||
|
timestamp_str,
|
||||||
|
longitude_str, // reverse order!
|
||||||
|
latitude_str,
|
||||||
|
);
|
||||||
|
|
||||||
|
free(latitude_str as *mut libc::c_void);
|
||||||
|
free(longitude_str as *mut libc::c_void);
|
||||||
|
free(timestamp_str as *mut libc::c_void);
|
||||||
|
|
||||||
|
ret
|
||||||
|
}
|
||||||
|
|
||||||
|
pub unsafe fn dc_set_kml_sent_timestamp(context: &Context, chat_id: uint32_t, timestamp: i64) {
|
||||||
|
let stmt = dc_sqlite3_prepare(
|
||||||
|
context,
|
||||||
|
&context.sql,
|
||||||
|
b"UPDATE chats SET locations_last_sent=? WHERE id=?;\x00" as *const u8
|
||||||
|
as *const libc::c_char,
|
||||||
|
);
|
||||||
|
sqlite3_bind_int64(stmt, 1i32, timestamp as sqlite3_int64);
|
||||||
|
sqlite3_bind_int(stmt, 2i32, chat_id as libc::c_int);
|
||||||
|
sqlite3_step(stmt);
|
||||||
|
sqlite3_finalize(stmt);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub unsafe fn dc_set_msg_location_id(context: &Context, msg_id: uint32_t, location_id: uint32_t) {
|
||||||
|
let stmt: *mut sqlite3_stmt;
|
||||||
|
stmt = dc_sqlite3_prepare(
|
||||||
|
context,
|
||||||
|
&context.sql,
|
||||||
|
b"UPDATE msgs SET location_id=? WHERE id=?;\x00" as *const u8 as *const libc::c_char,
|
||||||
|
);
|
||||||
|
sqlite3_bind_int64(stmt, 1i32, location_id as sqlite3_int64);
|
||||||
|
sqlite3_bind_int(stmt, 2i32, msg_id as libc::c_int);
|
||||||
|
sqlite3_step(stmt);
|
||||||
|
sqlite3_finalize(stmt);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub unsafe fn dc_save_locations(
|
||||||
|
context: &Context,
|
||||||
|
chat_id: uint32_t,
|
||||||
|
contact_id: uint32_t,
|
||||||
|
locations: *const dc_array_t,
|
||||||
|
independent: libc::c_int,
|
||||||
|
) -> uint32_t {
|
||||||
|
let mut stmt_test: *mut sqlite3_stmt = 0 as *mut sqlite3_stmt;
|
||||||
|
let mut stmt_insert: *mut sqlite3_stmt = 0 as *mut sqlite3_stmt;
|
||||||
|
let mut newest_timestamp = 0;
|
||||||
|
let mut newest_location_id: uint32_t = 0i32 as uint32_t;
|
||||||
|
if !(chat_id <= 9i32 as libc::c_uint || locations.is_null()) {
|
||||||
|
stmt_test = dc_sqlite3_prepare(
|
||||||
|
context,
|
||||||
|
&context.sql,
|
||||||
|
b"SELECT id FROM locations WHERE timestamp=? AND from_id=?\x00" as *const u8
|
||||||
|
as *const libc::c_char,
|
||||||
|
);
|
||||||
|
stmt_insert = dc_sqlite3_prepare(
|
||||||
|
context,
|
||||||
|
&context.sql,
|
||||||
|
b"INSERT INTO locations\
|
||||||
|
(timestamp, from_id, chat_id, latitude, longitude, accuracy, independent) \
|
||||||
|
VALUES (?,?,?,?,?,?,?);\x00" as *const u8 as *const libc::c_char,
|
||||||
|
);
|
||||||
|
let mut i = 0;
|
||||||
|
while i < dc_array_get_cnt(locations) {
|
||||||
|
let location: *mut dc_location_t =
|
||||||
|
dc_array_get_ptr(locations, i as size_t) as *mut dc_location_t;
|
||||||
|
sqlite3_reset(stmt_test);
|
||||||
|
sqlite3_bind_int64(stmt_test, 1i32, (*location).timestamp as sqlite3_int64);
|
||||||
|
sqlite3_bind_int(stmt_test, 2i32, contact_id as libc::c_int);
|
||||||
|
if independent | sqlite3_step(stmt_test) != 100i32 {
|
||||||
|
sqlite3_reset(stmt_insert);
|
||||||
|
sqlite3_bind_int64(stmt_insert, 1i32, (*location).timestamp as sqlite3_int64);
|
||||||
|
sqlite3_bind_int(stmt_insert, 2i32, contact_id as libc::c_int);
|
||||||
|
sqlite3_bind_int(stmt_insert, 3i32, chat_id as libc::c_int);
|
||||||
|
sqlite3_bind_double(stmt_insert, 4i32, (*location).latitude);
|
||||||
|
sqlite3_bind_double(stmt_insert, 5i32, (*location).longitude);
|
||||||
|
sqlite3_bind_double(stmt_insert, 6i32, (*location).accuracy);
|
||||||
|
sqlite3_bind_double(stmt_insert, 7i32, independent as libc::c_double);
|
||||||
|
sqlite3_step(stmt_insert);
|
||||||
|
}
|
||||||
|
if (*location).timestamp > newest_timestamp {
|
||||||
|
newest_timestamp = (*location).timestamp;
|
||||||
|
newest_location_id = dc_sqlite3_get_rowid2(
|
||||||
|
context,
|
||||||
|
&context.sql,
|
||||||
|
b"locations\x00" as *const u8 as *const libc::c_char,
|
||||||
|
b"timestamp\x00" as *const u8 as *const libc::c_char,
|
||||||
|
(*location).timestamp as uint64_t,
|
||||||
|
b"from_id\x00" as *const u8 as *const libc::c_char,
|
||||||
|
contact_id,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
i += 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sqlite3_finalize(stmt_test);
|
||||||
|
sqlite3_finalize(stmt_insert);
|
||||||
|
|
||||||
|
newest_location_id
|
||||||
|
}
|
||||||
|
|
||||||
|
pub unsafe fn dc_kml_parse(
|
||||||
|
context: &Context,
|
||||||
|
content: *const libc::c_char,
|
||||||
|
content_bytes: size_t,
|
||||||
|
) -> *mut dc_kml_t {
|
||||||
|
let mut kml: *mut dc_kml_t = calloc(1, ::std::mem::size_of::<dc_kml_t>()) as *mut dc_kml_t;
|
||||||
|
let mut content_nullterminated: *mut libc::c_char = 0 as *mut libc::c_char;
|
||||||
|
let mut saxparser: dc_saxparser_t = dc_saxparser_t {
|
||||||
|
starttag_cb: None,
|
||||||
|
endtag_cb: None,
|
||||||
|
text_cb: None,
|
||||||
|
userdata: 0 as *mut libc::c_void,
|
||||||
|
};
|
||||||
|
|
||||||
|
if content_bytes > (1 * 1024 * 1024) {
|
||||||
|
dc_log_warning(
|
||||||
|
context,
|
||||||
|
0,
|
||||||
|
b"A kml-files with %i bytes is larger than reasonably expected.\x00" as *const u8
|
||||||
|
as *const libc::c_char,
|
||||||
|
content_bytes,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
content_nullterminated = dc_null_terminate(content, content_bytes as libc::c_int);
|
||||||
|
if !content_nullterminated.is_null() {
|
||||||
|
(*kml).locations = dc_array_new_typed(1, 100 as size_t);
|
||||||
|
dc_saxparser_init(&mut saxparser, kml as *mut libc::c_void);
|
||||||
|
dc_saxparser_set_tag_handler(
|
||||||
|
&mut saxparser,
|
||||||
|
Some(kml_starttag_cb),
|
||||||
|
Some(kml_endtag_cb),
|
||||||
|
);
|
||||||
|
dc_saxparser_set_text_handler(&mut saxparser, Some(kml_text_cb));
|
||||||
|
dc_saxparser_parse(&mut saxparser, content_nullterminated);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
free(content_nullterminated as *mut libc::c_void);
|
||||||
|
|
||||||
|
kml
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe fn kml_text_cb(userdata: *mut libc::c_void, text: *const libc::c_char, _len: libc::c_int) {
|
||||||
|
let mut kml: *mut dc_kml_t = userdata as *mut dc_kml_t;
|
||||||
|
if 0 != (*kml).tag & (0x4 | 0x10) {
|
||||||
|
let mut val: *mut libc::c_char = dc_strdup(text);
|
||||||
|
dc_str_replace(
|
||||||
|
&mut val,
|
||||||
|
b"\n\x00" as *const u8 as *const libc::c_char,
|
||||||
|
b"\x00" as *const u8 as *const libc::c_char,
|
||||||
|
);
|
||||||
|
dc_str_replace(
|
||||||
|
&mut val,
|
||||||
|
b"\r\x00" as *const u8 as *const libc::c_char,
|
||||||
|
b"\x00" as *const u8 as *const libc::c_char,
|
||||||
|
);
|
||||||
|
dc_str_replace(
|
||||||
|
&mut val,
|
||||||
|
b"\t\x00" as *const u8 as *const libc::c_char,
|
||||||
|
b"\x00" as *const u8 as *const libc::c_char,
|
||||||
|
);
|
||||||
|
dc_str_replace(
|
||||||
|
&mut val,
|
||||||
|
b" \x00" as *const u8 as *const libc::c_char,
|
||||||
|
b"\x00" as *const u8 as *const libc::c_char,
|
||||||
|
);
|
||||||
|
if 0 != (*kml).tag & 0x4 && strlen(val) >= 19 {
|
||||||
|
// YYYY-MM-DDTHH:MM:SSZ
|
||||||
|
// 0 4 7 10 13 16 19
|
||||||
|
let val_r = as_str(val);
|
||||||
|
match chrono::NaiveDateTime::parse_from_str(val_r, "%Y-%m-%dT%H:%M:%SZ") {
|
||||||
|
Ok(res) => {
|
||||||
|
(*kml).curr.timestamp = res.timestamp();
|
||||||
|
if (*kml).curr.timestamp > time() {
|
||||||
|
(*kml).curr.timestamp = time();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(_err) => {
|
||||||
|
(*kml).curr.timestamp = time();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if 0 != (*kml).tag & 0x10i32 {
|
||||||
|
let mut comma: *mut libc::c_char = strchr(val, ',' as i32);
|
||||||
|
if !comma.is_null() {
|
||||||
|
let longitude: *mut libc::c_char = val;
|
||||||
|
let latitude: *mut libc::c_char = comma.offset(1isize);
|
||||||
|
*comma = 0i32 as libc::c_char;
|
||||||
|
comma = strchr(latitude, ',' as i32);
|
||||||
|
if !comma.is_null() {
|
||||||
|
*comma = 0i32 as libc::c_char
|
||||||
|
}
|
||||||
|
(*kml).curr.latitude = dc_atof(latitude);
|
||||||
|
(*kml).curr.longitude = dc_atof(longitude)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
free(val as *mut libc::c_void);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe fn kml_endtag_cb(userdata: *mut libc::c_void, tag: *const libc::c_char) {
|
||||||
|
let mut kml: *mut dc_kml_t = userdata as *mut dc_kml_t;
|
||||||
|
if strcmp(tag, b"placemark\x00" as *const u8 as *const libc::c_char) == 0i32 {
|
||||||
|
if 0 != (*kml).tag & 0x1i32
|
||||||
|
&& 0 != (*kml).curr.timestamp
|
||||||
|
&& 0. != (*kml).curr.latitude
|
||||||
|
&& 0. != (*kml).curr.longitude
|
||||||
|
{
|
||||||
|
let location: *mut dc_location_t =
|
||||||
|
calloc(1, ::std::mem::size_of::<dc_location_t>()) as *mut dc_location_t;
|
||||||
|
*location = (*kml).curr;
|
||||||
|
dc_array_add_ptr((*kml).locations, location as *mut libc::c_void);
|
||||||
|
}
|
||||||
|
(*kml).tag = 0i32
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
* parse kml-files
|
||||||
|
******************************************************************************/
|
||||||
|
unsafe fn kml_starttag_cb(
|
||||||
|
userdata: *mut libc::c_void,
|
||||||
|
tag: *const libc::c_char,
|
||||||
|
attr: *mut *mut libc::c_char,
|
||||||
|
) {
|
||||||
|
let mut kml: *mut dc_kml_t = userdata as *mut dc_kml_t;
|
||||||
|
if strcmp(tag, b"document\x00" as *const u8 as *const libc::c_char) == 0i32 {
|
||||||
|
let addr: *const libc::c_char =
|
||||||
|
dc_attr_find(attr, b"addr\x00" as *const u8 as *const libc::c_char);
|
||||||
|
if !addr.is_null() {
|
||||||
|
(*kml).addr = dc_strdup(addr)
|
||||||
|
}
|
||||||
|
} else if strcmp(tag, b"placemark\x00" as *const u8 as *const libc::c_char) == 0i32 {
|
||||||
|
(*kml).tag = 0x1i32;
|
||||||
|
(*kml).curr.timestamp = 0;
|
||||||
|
(*kml).curr.latitude = 0i32 as libc::c_double;
|
||||||
|
(*kml).curr.longitude = 0.0f64;
|
||||||
|
(*kml).curr.accuracy = 0.0f64
|
||||||
|
} else if strcmp(tag, b"timestamp\x00" as *const u8 as *const libc::c_char) == 0i32
|
||||||
|
&& 0 != (*kml).tag & 0x1i32
|
||||||
|
{
|
||||||
|
(*kml).tag = 0x1i32 | 0x2i32
|
||||||
|
} else if strcmp(tag, b"when\x00" as *const u8 as *const libc::c_char) == 0i32
|
||||||
|
&& 0 != (*kml).tag & 0x2i32
|
||||||
|
{
|
||||||
|
(*kml).tag = 0x1i32 | 0x2i32 | 0x4i32
|
||||||
|
} else if strcmp(tag, b"point\x00" as *const u8 as *const libc::c_char) == 0i32
|
||||||
|
&& 0 != (*kml).tag & 0x1i32
|
||||||
|
{
|
||||||
|
(*kml).tag = 0x1i32 | 0x8i32
|
||||||
|
} else if strcmp(tag, b"coordinates\x00" as *const u8 as *const libc::c_char) == 0i32
|
||||||
|
&& 0 != (*kml).tag & 0x8i32
|
||||||
|
{
|
||||||
|
(*kml).tag = 0x1i32 | 0x8i32 | 0x10i32;
|
||||||
|
let accuracy: *const libc::c_char =
|
||||||
|
dc_attr_find(attr, b"accuracy\x00" as *const u8 as *const libc::c_char);
|
||||||
|
if !accuracy.is_null() {
|
||||||
|
(*kml).curr.accuracy = dc_atof(accuracy)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub unsafe fn dc_kml_unref(kml: *mut dc_kml_t) {
|
||||||
|
if kml.is_null() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
dc_array_unref((*kml).locations);
|
||||||
|
free((*kml).addr as *mut libc::c_void);
|
||||||
|
free(kml as *mut libc::c_void);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub unsafe fn dc_job_do_DC_JOB_MAYBE_SEND_LOCATIONS(context: &Context, _job: *mut dc_job_t) {
|
||||||
|
let stmt_chats: *mut sqlite3_stmt;
|
||||||
|
let mut stmt_locations: *mut sqlite3_stmt = 0 as *mut sqlite3_stmt;
|
||||||
|
let now = time();
|
||||||
|
let mut continue_streaming: libc::c_int = 1i32;
|
||||||
|
dc_log_info(
|
||||||
|
context,
|
||||||
|
0i32,
|
||||||
|
b" ----------------- MAYBE_SEND_LOCATIONS -------------- \x00" as *const u8
|
||||||
|
as *const libc::c_char,
|
||||||
|
);
|
||||||
|
stmt_chats = dc_sqlite3_prepare(
|
||||||
|
context,
|
||||||
|
&context.sql,
|
||||||
|
b"SELECT id, locations_send_begin, locations_last_sent \
|
||||||
|
FROM chats \
|
||||||
|
WHERE locations_send_until>?;\x00" as *const u8 as *const libc::c_char,
|
||||||
|
);
|
||||||
|
sqlite3_bind_int64(stmt_chats, 1i32, now as sqlite3_int64);
|
||||||
|
while sqlite3_step(stmt_chats) == 100i32 {
|
||||||
|
let chat_id: uint32_t = sqlite3_column_int(stmt_chats, 0i32) as uint32_t;
|
||||||
|
let locations_send_begin = sqlite3_column_int64(stmt_chats, 1i32) as i64;
|
||||||
|
let locations_last_sent = sqlite3_column_int64(stmt_chats, 2i32) as i64;
|
||||||
|
continue_streaming = 1i32;
|
||||||
|
// be a bit tolerant as the timer may not align exactly with time(NULL)
|
||||||
|
if now - locations_last_sent < (60 - 3) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if stmt_locations.is_null() {
|
||||||
|
stmt_locations = dc_sqlite3_prepare(
|
||||||
|
context,
|
||||||
|
&context.sql,
|
||||||
|
b"SELECT id \
|
||||||
|
FROM locations \
|
||||||
|
WHERE from_id=? \
|
||||||
|
AND timestamp>=? \
|
||||||
|
AND timestamp>? \
|
||||||
|
AND independent=0 \
|
||||||
|
ORDER BY timestamp;\x00" as *const u8 as *const libc::c_char,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
sqlite3_reset(stmt_locations);
|
||||||
|
}
|
||||||
|
sqlite3_bind_int(stmt_locations, 1i32, 1i32);
|
||||||
|
sqlite3_bind_int64(stmt_locations, 2i32, locations_send_begin as sqlite3_int64);
|
||||||
|
sqlite3_bind_int64(stmt_locations, 3i32, locations_last_sent as sqlite3_int64);
|
||||||
|
// if there is no new location, there's nothing to send.
|
||||||
|
// however, maybe we want to bypass this test eg. 15 minutes
|
||||||
|
if sqlite3_step(stmt_locations) != 100i32 {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// pending locations are attached automatically to every message,
|
||||||
|
// so also to this empty text message.
|
||||||
|
// DC_CMD_LOCATION is only needed to create a nicer subject.
|
||||||
|
//
|
||||||
|
// for optimisation and to avoid flooding the sending queue,
|
||||||
|
// we could sending these messages only if we're really online.
|
||||||
|
// the easiest way to determine this, is to check for an empty message queue.
|
||||||
|
// (might not be 100%, however, as positions are sent combined later
|
||||||
|
// and dc_set_location() is typically called periodically, this is ok)
|
||||||
|
let mut msg: *mut dc_msg_t = dc_msg_new(context, 10i32);
|
||||||
|
(*msg).hidden = 1i32;
|
||||||
|
dc_param_set_int((*msg).param, 'S' as i32, 9i32);
|
||||||
|
dc_send_msg(context, chat_id, msg);
|
||||||
|
dc_msg_unref(msg);
|
||||||
|
}
|
||||||
|
if 0 != continue_streaming {
|
||||||
|
schedule_MAYBE_SEND_LOCATIONS(context, 0x1i32);
|
||||||
|
}
|
||||||
|
sqlite3_finalize(stmt_chats);
|
||||||
|
sqlite3_finalize(stmt_locations);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub unsafe fn dc_job_do_DC_JOB_MAYBE_SEND_LOC_ENDED(context: &Context, job: &mut dc_job_t) {
|
||||||
|
// this function is called when location-streaming _might_ have ended for a chat.
|
||||||
|
// the function checks, if location-streaming is really ended;
|
||||||
|
// if so, a device-message is added if not yet done.
|
||||||
|
let chat_id: uint32_t = (*job).foreign_id;
|
||||||
|
let locations_send_begin: i64;
|
||||||
|
let locations_send_until: i64;
|
||||||
|
let mut stmt;
|
||||||
|
let mut stock_str: *mut libc::c_char = 0 as *mut libc::c_char;
|
||||||
|
stmt = dc_sqlite3_prepare(
|
||||||
|
context,
|
||||||
|
&context.sql,
|
||||||
|
b"SELECT locations_send_begin, locations_send_until FROM chats WHERE id=?\x00"
|
||||||
|
as *const u8 as *const libc::c_char,
|
||||||
|
);
|
||||||
|
sqlite3_bind_int(stmt, 1i32, chat_id as libc::c_int);
|
||||||
|
if !(sqlite3_step(stmt) != 100i32) {
|
||||||
|
locations_send_begin = sqlite3_column_int64(stmt, 0i32) as i64;
|
||||||
|
locations_send_until = sqlite3_column_int64(stmt, 1i32) as i64;
|
||||||
|
sqlite3_finalize(stmt);
|
||||||
|
stmt = 0 as *mut sqlite3_stmt;
|
||||||
|
if !(locations_send_begin != 0 && time() <= locations_send_until) {
|
||||||
|
// still streaming -
|
||||||
|
// may happen as several calls to dc_send_locations_to_chat()
|
||||||
|
// do not un-schedule pending DC_MAYBE_SEND_LOC_ENDED jobs
|
||||||
|
if !(locations_send_begin == 0 && locations_send_until == 0) {
|
||||||
|
// not streaming, device-message already sent
|
||||||
|
stmt =
|
||||||
|
dc_sqlite3_prepare(
|
||||||
|
context,
|
||||||
|
&context.sql,
|
||||||
|
b"UPDATE chats SET locations_send_begin=0, locations_send_until=0 WHERE id=?\x00"
|
||||||
|
as *const u8 as
|
||||||
|
*const libc::c_char);
|
||||||
|
sqlite3_bind_int(stmt, 1i32, chat_id as libc::c_int);
|
||||||
|
sqlite3_step(stmt);
|
||||||
|
stock_str = dc_stock_system_msg(
|
||||||
|
context,
|
||||||
|
65i32,
|
||||||
|
0 as *const libc::c_char,
|
||||||
|
0 as *const libc::c_char,
|
||||||
|
0i32 as uint32_t,
|
||||||
|
);
|
||||||
|
dc_add_device_msg(context, chat_id, stock_str);
|
||||||
|
context.call_cb(
|
||||||
|
Event::CHAT_MODIFIED,
|
||||||
|
chat_id as uintptr_t,
|
||||||
|
0i32 as uintptr_t,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sqlite3_finalize(stmt);
|
||||||
|
free(stock_str as *mut libc::c_void);
|
||||||
|
}
|
||||||
142
src/dc_log.rs
Normal file
142
src/dc_log.rs
Normal file
@@ -0,0 +1,142 @@
|
|||||||
|
use crate::constants::Event;
|
||||||
|
use crate::context::Context;
|
||||||
|
use crate::dc_tools::*;
|
||||||
|
use crate::types::*;
|
||||||
|
use crate::x::*;
|
||||||
|
|
||||||
|
pub unsafe extern "C" fn dc_log_event(
|
||||||
|
context: &Context,
|
||||||
|
event_code: Event,
|
||||||
|
data1: libc::c_int,
|
||||||
|
msg: *const libc::c_char,
|
||||||
|
va: ...
|
||||||
|
) {
|
||||||
|
log_vprintf(context, event_code, data1, msg, va);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Asynchronous "Thread-errors" are reported by the dc_log_error()
|
||||||
|
function. These errors must be shown to the user by a bubble or so.
|
||||||
|
|
||||||
|
"Normal" errors are usually returned by a special value (null or so) and are
|
||||||
|
usually not reported using dc_log_error() - its up to the caller to
|
||||||
|
decide, what should be reported or done. However, these "Normal" errors
|
||||||
|
are usually logged by dc_log_warning(). */
|
||||||
|
unsafe fn log_vprintf(
|
||||||
|
context: &Context,
|
||||||
|
event: Event,
|
||||||
|
data1: libc::c_int,
|
||||||
|
msg_format: *const libc::c_char,
|
||||||
|
va_0: ::std::ffi::VaList,
|
||||||
|
) {
|
||||||
|
let msg: *mut libc::c_char;
|
||||||
|
if !msg_format.is_null() {
|
||||||
|
let mut tempbuf: [libc::c_char; 1025] = [0; 1025];
|
||||||
|
vsnprintf(
|
||||||
|
tempbuf.as_mut_ptr(),
|
||||||
|
1024i32 as libc::c_ulong,
|
||||||
|
msg_format,
|
||||||
|
va_0,
|
||||||
|
);
|
||||||
|
msg = dc_strdup(tempbuf.as_mut_ptr())
|
||||||
|
} else {
|
||||||
|
msg = dc_mprintf(
|
||||||
|
b"event #%i\x00" as *const u8 as *const libc::c_char,
|
||||||
|
event as libc::c_int,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
context.call_cb(event, data1 as uintptr_t, msg as uintptr_t);
|
||||||
|
free(msg as *mut libc::c_void);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub unsafe extern "C" fn dc_log_event_seq(
|
||||||
|
context: &Context,
|
||||||
|
event_code: Event,
|
||||||
|
sequence_start: *mut libc::c_int,
|
||||||
|
msg: *const libc::c_char,
|
||||||
|
va_0: ...
|
||||||
|
) {
|
||||||
|
if sequence_start.is_null() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
log_vprintf(context, event_code, *sequence_start, msg, va_0);
|
||||||
|
*sequence_start = 0i32;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub unsafe extern "C" fn dc_log_error(
|
||||||
|
context: &Context,
|
||||||
|
data1: libc::c_int,
|
||||||
|
msg: *const libc::c_char,
|
||||||
|
va_1: ...
|
||||||
|
) {
|
||||||
|
log_vprintf(context, Event::ERROR, data1, msg, va_1);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub unsafe extern "C" fn dc_log_warning(
|
||||||
|
context: &Context,
|
||||||
|
data1: libc::c_int,
|
||||||
|
msg: *const libc::c_char,
|
||||||
|
va_2: ...
|
||||||
|
) {
|
||||||
|
log_vprintf(context, Event::WARNING, data1, msg, va_2);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub unsafe extern "C" fn dc_log_info(
|
||||||
|
context: &Context,
|
||||||
|
data1: libc::c_int,
|
||||||
|
msg: *const libc::c_char,
|
||||||
|
va_3: ...
|
||||||
|
) {
|
||||||
|
log_vprintf(context, Event::INFO, data1, msg, va_3);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! info {
|
||||||
|
($ctx:expr, $data1:expr, $msg:expr) => {
|
||||||
|
info!($ctx, $data1, $msg,)
|
||||||
|
};
|
||||||
|
($ctx:expr, $data1:expr, $msg:expr, $($args:expr),* $(,)?) => {{
|
||||||
|
let formatted = format!($msg, $($args),*);
|
||||||
|
let formatted_c = $crate::dc_tools::to_cstring(formatted);
|
||||||
|
$ctx.call_cb($crate::constants::Event::INFO, $data1 as uintptr_t,
|
||||||
|
formatted_c.as_ptr() as uintptr_t)
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! warn {
|
||||||
|
($ctx:expr, $data1:expr, $msg:expr) => {
|
||||||
|
warn!($ctx, $data1, $msg,)
|
||||||
|
};
|
||||||
|
($ctx:expr, $data1:expr, $msg:expr, $($args:expr),* $(,)?) => {
|
||||||
|
let formatted = format!($msg, $($args),*);
|
||||||
|
let formatted_c = $crate::dc_tools::to_cstring(formatted);
|
||||||
|
$ctx.call_cb($crate::constants::Event::WARNING, $data1 as libc::uintptr_t,
|
||||||
|
formatted_c.as_ptr() as libc::uintptr_t)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! error {
|
||||||
|
($ctx:expr, $data1:expr, $msg:expr) => {
|
||||||
|
error!($ctx, $data1, $msg,)
|
||||||
|
};
|
||||||
|
($ctx:expr, $data1:expr, $msg:expr, $($args:expr),* $(,)?) => {
|
||||||
|
let formatted = format!($msg, $($args),*);
|
||||||
|
let formatted_c = $crate::dc_tools::to_cstring(formatted);
|
||||||
|
$ctx.call_cb($crate::constants::Event::ERROR, $data1 as uintptr_t,
|
||||||
|
formatted_c.as_ptr() as uintptr_t)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! log_event {
|
||||||
|
($ctx:expr, $data1:expr, $msg:expr) => {
|
||||||
|
log_event!($ctx, $data1, $msg,)
|
||||||
|
};
|
||||||
|
($ctx:expr, $event:expr, $data1:expr, $msg:expr, $($args:expr),* $(,)?) => {
|
||||||
|
let formatted = format!($msg, $($args),*);
|
||||||
|
let formatted_c = $crate::dc_tools::to_cstring(formatted);
|
||||||
|
$ctx.call_cb($event, $data1 as uintptr_t,
|
||||||
|
formatted_c.as_ptr() as uintptr_t)
|
||||||
|
};
|
||||||
|
}
|
||||||
323
src/dc_loginparam.rs
Normal file
323
src/dc_loginparam.rs
Normal file
@@ -0,0 +1,323 @@
|
|||||||
|
use crate::context::Context;
|
||||||
|
use crate::dc_sqlite3::*;
|
||||||
|
use crate::dc_tools::*;
|
||||||
|
use crate::types::*;
|
||||||
|
use crate::x::*;
|
||||||
|
|
||||||
|
#[derive(Copy, Clone)]
|
||||||
|
#[repr(C)]
|
||||||
|
pub struct dc_loginparam_t {
|
||||||
|
pub addr: *mut libc::c_char,
|
||||||
|
pub mail_server: *mut libc::c_char,
|
||||||
|
pub mail_user: *mut libc::c_char,
|
||||||
|
pub mail_pw: *mut libc::c_char,
|
||||||
|
pub mail_port: i32,
|
||||||
|
pub send_server: *mut libc::c_char,
|
||||||
|
pub send_user: *mut libc::c_char,
|
||||||
|
pub send_pw: *mut libc::c_char,
|
||||||
|
pub send_port: i32,
|
||||||
|
pub server_flags: i32,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub unsafe fn dc_loginparam_new() -> *mut dc_loginparam_t {
|
||||||
|
let loginparam: *mut dc_loginparam_t;
|
||||||
|
loginparam = calloc(1, ::std::mem::size_of::<dc_loginparam_t>()) as *mut dc_loginparam_t;
|
||||||
|
assert!(!loginparam.is_null());
|
||||||
|
|
||||||
|
loginparam
|
||||||
|
}
|
||||||
|
|
||||||
|
pub unsafe fn dc_loginparam_unref(loginparam: *mut dc_loginparam_t) {
|
||||||
|
if loginparam.is_null() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
dc_loginparam_empty(loginparam);
|
||||||
|
free(loginparam as *mut libc::c_void);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* clears all data and frees its memory. All pointers are NULL after this function is called. */
|
||||||
|
pub unsafe fn dc_loginparam_empty(mut loginparam: *mut dc_loginparam_t) {
|
||||||
|
if loginparam.is_null() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
free((*loginparam).addr as *mut libc::c_void);
|
||||||
|
(*loginparam).addr = 0 as *mut libc::c_char;
|
||||||
|
free((*loginparam).mail_server as *mut libc::c_void);
|
||||||
|
(*loginparam).mail_server = 0 as *mut libc::c_char;
|
||||||
|
(*loginparam).mail_port = 0i32;
|
||||||
|
free((*loginparam).mail_user as *mut libc::c_void);
|
||||||
|
(*loginparam).mail_user = 0 as *mut libc::c_char;
|
||||||
|
free((*loginparam).mail_pw as *mut libc::c_void);
|
||||||
|
(*loginparam).mail_pw = 0 as *mut libc::c_char;
|
||||||
|
free((*loginparam).send_server as *mut libc::c_void);
|
||||||
|
(*loginparam).send_server = 0 as *mut libc::c_char;
|
||||||
|
(*loginparam).send_port = 0i32;
|
||||||
|
free((*loginparam).send_user as *mut libc::c_void);
|
||||||
|
(*loginparam).send_user = 0 as *mut libc::c_char;
|
||||||
|
free((*loginparam).send_pw as *mut libc::c_void);
|
||||||
|
(*loginparam).send_pw = 0 as *mut libc::c_char;
|
||||||
|
(*loginparam).server_flags = 0i32;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub unsafe fn dc_loginparam_read(
|
||||||
|
context: &Context,
|
||||||
|
loginparam: *mut dc_loginparam_t,
|
||||||
|
sql: &SQLite,
|
||||||
|
prefix: *const libc::c_char,
|
||||||
|
) {
|
||||||
|
let mut key: *mut libc::c_char = 0 as *mut libc::c_char;
|
||||||
|
dc_loginparam_empty(loginparam);
|
||||||
|
sqlite3_free(key as *mut libc::c_void);
|
||||||
|
key = sqlite3_mprintf(
|
||||||
|
b"%s%s\x00" as *const u8 as *const libc::c_char,
|
||||||
|
prefix,
|
||||||
|
b"addr\x00" as *const u8 as *const libc::c_char,
|
||||||
|
);
|
||||||
|
(*loginparam).addr = dc_sqlite3_get_config(context, sql, key, 0 as *const libc::c_char);
|
||||||
|
sqlite3_free(key as *mut libc::c_void);
|
||||||
|
key = sqlite3_mprintf(
|
||||||
|
b"%s%s\x00" as *const u8 as *const libc::c_char,
|
||||||
|
prefix,
|
||||||
|
b"mail_server\x00" as *const u8 as *const libc::c_char,
|
||||||
|
);
|
||||||
|
(*loginparam).mail_server = dc_sqlite3_get_config(context, sql, key, 0 as *const libc::c_char);
|
||||||
|
sqlite3_free(key as *mut libc::c_void);
|
||||||
|
key = sqlite3_mprintf(
|
||||||
|
b"%s%s\x00" as *const u8 as *const libc::c_char,
|
||||||
|
prefix,
|
||||||
|
b"mail_port\x00" as *const u8 as *const libc::c_char,
|
||||||
|
);
|
||||||
|
(*loginparam).mail_port = dc_sqlite3_get_config_int(context, sql, key, 0i32);
|
||||||
|
sqlite3_free(key as *mut libc::c_void);
|
||||||
|
key = sqlite3_mprintf(
|
||||||
|
b"%s%s\x00" as *const u8 as *const libc::c_char,
|
||||||
|
prefix,
|
||||||
|
b"mail_user\x00" as *const u8 as *const libc::c_char,
|
||||||
|
);
|
||||||
|
(*loginparam).mail_user = dc_sqlite3_get_config(context, sql, key, 0 as *const libc::c_char);
|
||||||
|
sqlite3_free(key as *mut libc::c_void);
|
||||||
|
key = sqlite3_mprintf(
|
||||||
|
b"%s%s\x00" as *const u8 as *const libc::c_char,
|
||||||
|
prefix,
|
||||||
|
b"mail_pw\x00" as *const u8 as *const libc::c_char,
|
||||||
|
);
|
||||||
|
(*loginparam).mail_pw = dc_sqlite3_get_config(context, sql, key, 0 as *const libc::c_char);
|
||||||
|
sqlite3_free(key as *mut libc::c_void);
|
||||||
|
key = sqlite3_mprintf(
|
||||||
|
b"%s%s\x00" as *const u8 as *const libc::c_char,
|
||||||
|
prefix,
|
||||||
|
b"send_server\x00" as *const u8 as *const libc::c_char,
|
||||||
|
);
|
||||||
|
(*loginparam).send_server = dc_sqlite3_get_config(context, sql, key, 0 as *const libc::c_char);
|
||||||
|
sqlite3_free(key as *mut libc::c_void);
|
||||||
|
key = sqlite3_mprintf(
|
||||||
|
b"%s%s\x00" as *const u8 as *const libc::c_char,
|
||||||
|
prefix,
|
||||||
|
b"send_port\x00" as *const u8 as *const libc::c_char,
|
||||||
|
);
|
||||||
|
(*loginparam).send_port = dc_sqlite3_get_config_int(context, sql, key, 0i32);
|
||||||
|
sqlite3_free(key as *mut libc::c_void);
|
||||||
|
key = sqlite3_mprintf(
|
||||||
|
b"%s%s\x00" as *const u8 as *const libc::c_char,
|
||||||
|
prefix,
|
||||||
|
b"send_user\x00" as *const u8 as *const libc::c_char,
|
||||||
|
);
|
||||||
|
(*loginparam).send_user = dc_sqlite3_get_config(context, sql, key, 0 as *const libc::c_char);
|
||||||
|
sqlite3_free(key as *mut libc::c_void);
|
||||||
|
key = sqlite3_mprintf(
|
||||||
|
b"%s%s\x00" as *const u8 as *const libc::c_char,
|
||||||
|
prefix,
|
||||||
|
b"send_pw\x00" as *const u8 as *const libc::c_char,
|
||||||
|
);
|
||||||
|
(*loginparam).send_pw = dc_sqlite3_get_config(context, sql, key, 0 as *const libc::c_char);
|
||||||
|
sqlite3_free(key as *mut libc::c_void);
|
||||||
|
key = sqlite3_mprintf(
|
||||||
|
b"%s%s\x00" as *const u8 as *const libc::c_char,
|
||||||
|
prefix,
|
||||||
|
b"server_flags\x00" as *const u8 as *const libc::c_char,
|
||||||
|
);
|
||||||
|
(*loginparam).server_flags = dc_sqlite3_get_config_int(context, sql, key, 0i32);
|
||||||
|
sqlite3_free(key as *mut libc::c_void);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub unsafe fn dc_loginparam_write(
|
||||||
|
context: &Context,
|
||||||
|
loginparam: *const dc_loginparam_t,
|
||||||
|
sql: &SQLite,
|
||||||
|
prefix: *const libc::c_char,
|
||||||
|
) {
|
||||||
|
let mut key: *mut libc::c_char = 0 as *mut libc::c_char;
|
||||||
|
sqlite3_free(key as *mut libc::c_void);
|
||||||
|
key = sqlite3_mprintf(
|
||||||
|
b"%s%s\x00" as *const u8 as *const libc::c_char,
|
||||||
|
prefix,
|
||||||
|
b"addr\x00" as *const u8 as *const libc::c_char,
|
||||||
|
);
|
||||||
|
dc_sqlite3_set_config(context, sql, key, (*loginparam).addr);
|
||||||
|
sqlite3_free(key as *mut libc::c_void);
|
||||||
|
key = sqlite3_mprintf(
|
||||||
|
b"%s%s\x00" as *const u8 as *const libc::c_char,
|
||||||
|
prefix,
|
||||||
|
b"mail_server\x00" as *const u8 as *const libc::c_char,
|
||||||
|
);
|
||||||
|
dc_sqlite3_set_config(context, sql, key, (*loginparam).mail_server);
|
||||||
|
sqlite3_free(key as *mut libc::c_void);
|
||||||
|
key = sqlite3_mprintf(
|
||||||
|
b"%s%s\x00" as *const u8 as *const libc::c_char,
|
||||||
|
prefix,
|
||||||
|
b"mail_port\x00" as *const u8 as *const libc::c_char,
|
||||||
|
);
|
||||||
|
dc_sqlite3_set_config_int(context, sql, key, (*loginparam).mail_port);
|
||||||
|
sqlite3_free(key as *mut libc::c_void);
|
||||||
|
key = sqlite3_mprintf(
|
||||||
|
b"%s%s\x00" as *const u8 as *const libc::c_char,
|
||||||
|
prefix,
|
||||||
|
b"mail_user\x00" as *const u8 as *const libc::c_char,
|
||||||
|
);
|
||||||
|
dc_sqlite3_set_config(context, sql, key, (*loginparam).mail_user);
|
||||||
|
sqlite3_free(key as *mut libc::c_void);
|
||||||
|
key = sqlite3_mprintf(
|
||||||
|
b"%s%s\x00" as *const u8 as *const libc::c_char,
|
||||||
|
prefix,
|
||||||
|
b"mail_pw\x00" as *const u8 as *const libc::c_char,
|
||||||
|
);
|
||||||
|
dc_sqlite3_set_config(context, sql, key, (*loginparam).mail_pw);
|
||||||
|
sqlite3_free(key as *mut libc::c_void);
|
||||||
|
key = sqlite3_mprintf(
|
||||||
|
b"%s%s\x00" as *const u8 as *const libc::c_char,
|
||||||
|
prefix,
|
||||||
|
b"send_server\x00" as *const u8 as *const libc::c_char,
|
||||||
|
);
|
||||||
|
dc_sqlite3_set_config(context, sql, key, (*loginparam).send_server);
|
||||||
|
sqlite3_free(key as *mut libc::c_void);
|
||||||
|
key = sqlite3_mprintf(
|
||||||
|
b"%s%s\x00" as *const u8 as *const libc::c_char,
|
||||||
|
prefix,
|
||||||
|
b"send_port\x00" as *const u8 as *const libc::c_char,
|
||||||
|
);
|
||||||
|
dc_sqlite3_set_config_int(context, sql, key, (*loginparam).send_port);
|
||||||
|
sqlite3_free(key as *mut libc::c_void);
|
||||||
|
key = sqlite3_mprintf(
|
||||||
|
b"%s%s\x00" as *const u8 as *const libc::c_char,
|
||||||
|
prefix,
|
||||||
|
b"send_user\x00" as *const u8 as *const libc::c_char,
|
||||||
|
);
|
||||||
|
dc_sqlite3_set_config(context, sql, key, (*loginparam).send_user);
|
||||||
|
sqlite3_free(key as *mut libc::c_void);
|
||||||
|
key = sqlite3_mprintf(
|
||||||
|
b"%s%s\x00" as *const u8 as *const libc::c_char,
|
||||||
|
prefix,
|
||||||
|
b"send_pw\x00" as *const u8 as *const libc::c_char,
|
||||||
|
);
|
||||||
|
dc_sqlite3_set_config(context, sql, key, (*loginparam).send_pw);
|
||||||
|
sqlite3_free(key as *mut libc::c_void);
|
||||||
|
key = sqlite3_mprintf(
|
||||||
|
b"%s%s\x00" as *const u8 as *const libc::c_char,
|
||||||
|
prefix,
|
||||||
|
b"server_flags\x00" as *const u8 as *const libc::c_char,
|
||||||
|
);
|
||||||
|
dc_sqlite3_set_config_int(context, sql, key, (*loginparam).server_flags);
|
||||||
|
sqlite3_free(key as *mut libc::c_void);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub unsafe fn dc_loginparam_get_readable(loginparam: *const dc_loginparam_t) -> *mut libc::c_char {
|
||||||
|
let unset: *const libc::c_char = b"0\x00" as *const u8 as *const libc::c_char;
|
||||||
|
let pw: *const libc::c_char = b"***\x00" as *const u8 as *const libc::c_char;
|
||||||
|
if loginparam.is_null() {
|
||||||
|
return dc_strdup(0 as *const libc::c_char);
|
||||||
|
}
|
||||||
|
let flags_readable: *mut libc::c_char = get_readable_flags((*loginparam).server_flags);
|
||||||
|
let ret: *mut libc::c_char = dc_mprintf(
|
||||||
|
b"%s %s:%s:%s:%i %s:%s:%s:%i %s\x00" as *const u8 as *const libc::c_char,
|
||||||
|
if !(*loginparam).addr.is_null() {
|
||||||
|
(*loginparam).addr
|
||||||
|
} else {
|
||||||
|
unset
|
||||||
|
},
|
||||||
|
if !(*loginparam).mail_user.is_null() {
|
||||||
|
(*loginparam).mail_user
|
||||||
|
} else {
|
||||||
|
unset
|
||||||
|
},
|
||||||
|
if !(*loginparam).mail_pw.is_null() {
|
||||||
|
pw
|
||||||
|
} else {
|
||||||
|
unset
|
||||||
|
},
|
||||||
|
if !(*loginparam).mail_server.is_null() {
|
||||||
|
(*loginparam).mail_server
|
||||||
|
} else {
|
||||||
|
unset
|
||||||
|
},
|
||||||
|
(*loginparam).mail_port,
|
||||||
|
if !(*loginparam).send_user.is_null() {
|
||||||
|
(*loginparam).send_user
|
||||||
|
} else {
|
||||||
|
unset
|
||||||
|
},
|
||||||
|
if !(*loginparam).send_pw.is_null() {
|
||||||
|
pw
|
||||||
|
} else {
|
||||||
|
unset
|
||||||
|
},
|
||||||
|
if !(*loginparam).send_server.is_null() {
|
||||||
|
(*loginparam).send_server
|
||||||
|
} else {
|
||||||
|
unset
|
||||||
|
},
|
||||||
|
(*loginparam).send_port,
|
||||||
|
flags_readable,
|
||||||
|
);
|
||||||
|
free(flags_readable as *mut libc::c_void);
|
||||||
|
|
||||||
|
ret
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_readable_flags(flags: libc::c_int) -> *mut libc::c_char {
|
||||||
|
let mut res = String::new();
|
||||||
|
for bit in 0..31 {
|
||||||
|
if 0 != flags & 1 << bit {
|
||||||
|
let mut flag_added: libc::c_int = 0;
|
||||||
|
if 1 << bit == 0x2 {
|
||||||
|
res += "OAUTH2 ";
|
||||||
|
flag_added = 1;
|
||||||
|
}
|
||||||
|
if 1 << bit == 0x4 {
|
||||||
|
res += "AUTH_NORMAL ";
|
||||||
|
flag_added = 1;
|
||||||
|
}
|
||||||
|
if 1 << bit == 0x100 {
|
||||||
|
res += "IMAP_STARTTLS ";
|
||||||
|
flag_added = 1;
|
||||||
|
}
|
||||||
|
if 1 << bit == 0x200 {
|
||||||
|
res += "IMAP_SSL ";
|
||||||
|
flag_added = 1;
|
||||||
|
}
|
||||||
|
if 1 << bit == 0x400 {
|
||||||
|
res += "IMAP_PLAIN ";
|
||||||
|
flag_added = 1;
|
||||||
|
}
|
||||||
|
if 1 << bit == 0x10000 {
|
||||||
|
res += "SMTP_STARTTLS ";
|
||||||
|
flag_added = 1
|
||||||
|
}
|
||||||
|
if 1 << bit == 0x20000 {
|
||||||
|
res += "SMTP_SSL ";
|
||||||
|
flag_added = 1
|
||||||
|
}
|
||||||
|
if 1 << bit == 0x40000 {
|
||||||
|
res += "SMTP_PLAIN ";
|
||||||
|
flag_added = 1
|
||||||
|
}
|
||||||
|
if 0 == flag_added {
|
||||||
|
res += &format!("{:#0x}", 1 << bit);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if res.is_empty() {
|
||||||
|
res += "0";
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe { strdup(to_cstring(res).as_ptr()) }
|
||||||
|
}
|
||||||
167
src/dc_lot.rs
Normal file
167
src/dc_lot.rs
Normal file
@@ -0,0 +1,167 @@
|
|||||||
|
use crate::context::Context;
|
||||||
|
use crate::dc_chat::*;
|
||||||
|
use crate::dc_contact::*;
|
||||||
|
use crate::dc_msg::*;
|
||||||
|
use crate::dc_stock::*;
|
||||||
|
use crate::dc_tools::*;
|
||||||
|
use crate::types::*;
|
||||||
|
use crate::x::*;
|
||||||
|
|
||||||
|
/* * Structure behind dc_lot_t */
|
||||||
|
#[derive(Copy, Clone)]
|
||||||
|
#[repr(C)]
|
||||||
|
pub struct dc_lot_t {
|
||||||
|
pub magic: uint32_t,
|
||||||
|
pub text1_meaning: libc::c_int,
|
||||||
|
pub text1: *mut libc::c_char,
|
||||||
|
pub text2: *mut libc::c_char,
|
||||||
|
pub timestamp: i64,
|
||||||
|
pub state: libc::c_int,
|
||||||
|
pub id: uint32_t,
|
||||||
|
pub fingerprint: *mut libc::c_char,
|
||||||
|
pub invitenumber: *mut libc::c_char,
|
||||||
|
pub auth: *mut libc::c_char,
|
||||||
|
}
|
||||||
|
|
||||||
|
/* *
|
||||||
|
* @class dc_lot_t
|
||||||
|
*
|
||||||
|
* 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 dc_chatlist_get_summary() or dc_msg_get_summary().
|
||||||
|
*
|
||||||
|
* NB: _Lot_ is used in the meaning _heap_ here.
|
||||||
|
*/
|
||||||
|
pub unsafe fn dc_lot_new() -> *mut dc_lot_t {
|
||||||
|
let mut lot: *mut dc_lot_t;
|
||||||
|
lot = calloc(1, ::std::mem::size_of::<dc_lot_t>()) as *mut dc_lot_t;
|
||||||
|
assert!(!lot.is_null());
|
||||||
|
|
||||||
|
(*lot).magic = 0x107107i32 as uint32_t;
|
||||||
|
(*lot).text1_meaning = 0i32;
|
||||||
|
|
||||||
|
lot
|
||||||
|
}
|
||||||
|
|
||||||
|
pub unsafe fn dc_lot_empty(mut lot: *mut dc_lot_t) {
|
||||||
|
if lot.is_null() || (*lot).magic != 0x107107i32 as libc::c_uint {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
free((*lot).text1 as *mut libc::c_void);
|
||||||
|
(*lot).text1 = 0 as *mut libc::c_char;
|
||||||
|
(*lot).text1_meaning = 0i32;
|
||||||
|
free((*lot).text2 as *mut libc::c_void);
|
||||||
|
(*lot).text2 = 0 as *mut libc::c_char;
|
||||||
|
free((*lot).fingerprint as *mut libc::c_void);
|
||||||
|
(*lot).fingerprint = 0 as *mut libc::c_char;
|
||||||
|
free((*lot).invitenumber as *mut libc::c_void);
|
||||||
|
(*lot).invitenumber = 0 as *mut libc::c_char;
|
||||||
|
free((*lot).auth as *mut libc::c_void);
|
||||||
|
(*lot).auth = 0 as *mut libc::c_char;
|
||||||
|
(*lot).timestamp = 0;
|
||||||
|
(*lot).state = 0i32;
|
||||||
|
(*lot).id = 0i32 as uint32_t;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub unsafe fn dc_lot_unref(mut set: *mut dc_lot_t) {
|
||||||
|
if set.is_null() || (*set).magic != 0x107107i32 as libc::c_uint {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
dc_lot_empty(set);
|
||||||
|
(*set).magic = 0i32 as uint32_t;
|
||||||
|
free(set as *mut libc::c_void);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub unsafe fn dc_lot_get_text1(lot: *const dc_lot_t) -> *mut libc::c_char {
|
||||||
|
if lot.is_null() || (*lot).magic != 0x107107i32 as libc::c_uint {
|
||||||
|
return 0 as *mut libc::c_char;
|
||||||
|
}
|
||||||
|
|
||||||
|
dc_strdup_keep_null((*lot).text1)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub unsafe fn dc_lot_get_text2(lot: *const dc_lot_t) -> *mut libc::c_char {
|
||||||
|
if lot.is_null() || (*lot).magic != 0x107107i32 as libc::c_uint {
|
||||||
|
return 0 as *mut libc::c_char;
|
||||||
|
}
|
||||||
|
|
||||||
|
dc_strdup_keep_null((*lot).text2)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub unsafe fn dc_lot_get_text1_meaning(lot: *const dc_lot_t) -> libc::c_int {
|
||||||
|
if lot.is_null() || (*lot).magic != 0x107107i32 as libc::c_uint {
|
||||||
|
return 0i32;
|
||||||
|
}
|
||||||
|
|
||||||
|
(*lot).text1_meaning
|
||||||
|
}
|
||||||
|
|
||||||
|
pub unsafe fn dc_lot_get_state(lot: *const dc_lot_t) -> libc::c_int {
|
||||||
|
if lot.is_null() || (*lot).magic != 0x107107i32 as libc::c_uint {
|
||||||
|
return 0i32;
|
||||||
|
}
|
||||||
|
|
||||||
|
(*lot).state
|
||||||
|
}
|
||||||
|
|
||||||
|
pub unsafe fn dc_lot_get_id(lot: *const dc_lot_t) -> uint32_t {
|
||||||
|
if lot.is_null() || (*lot).magic != 0x107107i32 as libc::c_uint {
|
||||||
|
return 0i32 as uint32_t;
|
||||||
|
}
|
||||||
|
|
||||||
|
(*lot).id
|
||||||
|
}
|
||||||
|
|
||||||
|
pub unsafe fn dc_lot_get_timestamp(lot: *const dc_lot_t) -> i64 {
|
||||||
|
if lot.is_null() || (*lot).magic != 0x107107i32 as libc::c_uint {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
(*lot).timestamp
|
||||||
|
}
|
||||||
|
|
||||||
|
/* library-internal */
|
||||||
|
/* in practice, the user additionally cuts the string himself pixel-accurate */
|
||||||
|
pub unsafe fn dc_lot_fill(
|
||||||
|
mut lot: *mut dc_lot_t,
|
||||||
|
msg: *const dc_msg_t,
|
||||||
|
chat: *const Chat,
|
||||||
|
contact: *const dc_contact_t,
|
||||||
|
context: &Context,
|
||||||
|
) {
|
||||||
|
if lot.is_null() || (*lot).magic != 0x107107i32 as libc::c_uint || msg.is_null() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (*msg).state == 19i32 {
|
||||||
|
(*lot).text1 = dc_stock_str(context, 3i32);
|
||||||
|
(*lot).text1_meaning = 1i32
|
||||||
|
} else if (*msg).from_id == 1i32 as libc::c_uint {
|
||||||
|
if 0 != dc_msg_is_info(msg) || 0 != dc_chat_is_self_talk(chat) {
|
||||||
|
(*lot).text1 = 0 as *mut libc::c_char;
|
||||||
|
(*lot).text1_meaning = 0i32
|
||||||
|
} else {
|
||||||
|
(*lot).text1 = dc_stock_str(context, 2i32);
|
||||||
|
(*lot).text1_meaning = 3i32
|
||||||
|
}
|
||||||
|
} else if chat.is_null() {
|
||||||
|
(*lot).text1 = 0 as *mut libc::c_char;
|
||||||
|
(*lot).text1_meaning = 0i32
|
||||||
|
} else if (*chat).type_0 == 120i32 || (*chat).type_0 == 130i32 {
|
||||||
|
if 0 != dc_msg_is_info(msg) || contact.is_null() {
|
||||||
|
(*lot).text1 = 0 as *mut libc::c_char;
|
||||||
|
(*lot).text1_meaning = 0i32
|
||||||
|
} else {
|
||||||
|
if !chat.is_null() && (*chat).id == 1i32 as libc::c_uint {
|
||||||
|
(*lot).text1 = dc_contact_get_display_name(contact)
|
||||||
|
} else {
|
||||||
|
(*lot).text1 = dc_contact_get_first_name(contact)
|
||||||
|
}
|
||||||
|
(*lot).text1_meaning = 2i32
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(*lot).text2 =
|
||||||
|
dc_msg_get_summarytext_by_raw((*msg).type_0, (*msg).text, (*msg).param, 160i32, context);
|
||||||
|
(*lot).timestamp = dc_msg_get_timestamp(msg);
|
||||||
|
(*lot).state = (*msg).state;
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
2663
src/dc_mimeparser.rs
2663
src/dc_mimeparser.rs
File diff suppressed because it is too large
Load Diff
45
src/dc_move.rs
Normal file
45
src/dc_move.rs
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
use crate::constants::*;
|
||||||
|
use crate::context::*;
|
||||||
|
use crate::dc_job::*;
|
||||||
|
use crate::dc_msg::*;
|
||||||
|
use crate::dc_sqlite3::*;
|
||||||
|
use crate::types::*;
|
||||||
|
|
||||||
|
pub unsafe fn dc_do_heuristics_moves(
|
||||||
|
context: &Context,
|
||||||
|
folder: *const libc::c_char,
|
||||||
|
msg_id: uint32_t,
|
||||||
|
) {
|
||||||
|
// for already seen messages, folder may be different from msg->folder
|
||||||
|
let mut msg: *mut dc_msg_t = 0 as *mut dc_msg_t;
|
||||||
|
let stmt: *mut sqlite3_stmt = 0 as *mut sqlite3_stmt;
|
||||||
|
if !(dc_sqlite3_get_config_int(
|
||||||
|
context,
|
||||||
|
&context.sql,
|
||||||
|
b"mvbox_move\x00" as *const u8 as *const libc::c_char,
|
||||||
|
1i32,
|
||||||
|
) == 0i32)
|
||||||
|
{
|
||||||
|
if !(0 == dc_is_inbox(context, folder) && 0 == dc_is_sentbox(context, folder)) {
|
||||||
|
msg = dc_msg_new_load(context, msg_id);
|
||||||
|
if !(0 != dc_msg_is_setupmessage(msg)) {
|
||||||
|
// do not move setup messages;
|
||||||
|
// there may be a non-delta device that wants to handle it
|
||||||
|
if 0 != dc_is_mvbox(context, folder) {
|
||||||
|
dc_update_msg_move_state(context, (*msg).rfc724_mid, DC_MOVE_STATE_STAY);
|
||||||
|
} else if 0 != (*msg).is_dc_message {
|
||||||
|
dc_job_add(
|
||||||
|
context,
|
||||||
|
200i32,
|
||||||
|
(*msg).id as libc::c_int,
|
||||||
|
0 as *const libc::c_char,
|
||||||
|
0i32,
|
||||||
|
);
|
||||||
|
dc_update_msg_move_state(context, (*msg).rfc724_mid, DC_MOVE_STATE_MOVING);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sqlite3_finalize(stmt);
|
||||||
|
dc_msg_unref(msg);
|
||||||
|
}
|
||||||
1648
src/dc_msg.rs
Normal file
1648
src/dc_msg.rs
Normal file
File diff suppressed because it is too large
Load Diff
437
src/dc_param.rs
Normal file
437
src/dc_param.rs
Normal file
@@ -0,0 +1,437 @@
|
|||||||
|
use crate::dc_tools::*;
|
||||||
|
use crate::types::*;
|
||||||
|
use crate::x::*;
|
||||||
|
|
||||||
|
/// for msgs and jobs
|
||||||
|
pub const DC_PARAM_FILE: char = 'f';
|
||||||
|
/// for msgs
|
||||||
|
pub const DC_PARAM_WIDTH: char = 'w';
|
||||||
|
/// for msgs
|
||||||
|
pub const DC_PARAM_HEIGHT: char = 'h';
|
||||||
|
/// for msgs
|
||||||
|
pub const DC_PARAM_DURATION: char = 'd';
|
||||||
|
/// for msgs
|
||||||
|
pub const DC_PARAM_MIMETYPE: char = 'm';
|
||||||
|
/// for msgs: incoming: message is encryoted, outgoing: guarantee E2EE or the message is not send
|
||||||
|
pub const DC_PARAM_GUARANTEE_E2EE: char = 'c';
|
||||||
|
/// for msgs: decrypted with validation errors or without mutual set, if neither 'c' nor 'e' are preset, the messages is only transport encrypted
|
||||||
|
pub const DC_PARAM_ERRONEOUS_E2EE: char = 'e';
|
||||||
|
/// for msgs: force unencrypted message, either DC_FP_ADD_AUTOCRYPT_HEADER (1), DC_FP_NO_AUTOCRYPT_HEADER (2) or 0
|
||||||
|
pub const DC_PARAM_FORCE_PLAINTEXT: char = 'u';
|
||||||
|
/// for msgs: an incoming message which requestes a MDN (aka read receipt)
|
||||||
|
pub const DC_PARAM_WANTS_MDN: char = 'r';
|
||||||
|
/// for msgs
|
||||||
|
pub const DC_PARAM_FORWARDED: char = 'a';
|
||||||
|
/// for msgs
|
||||||
|
pub const DC_PARAM_CMD: char = 'S';
|
||||||
|
/// for msgs
|
||||||
|
pub const DC_PARAM_CMD_ARG: char = 'E';
|
||||||
|
/// for msgs
|
||||||
|
pub const DC_PARAM_CMD_ARG2: char = 'F';
|
||||||
|
/// for msgs
|
||||||
|
pub const DC_PARAM_CMD_ARG3: char = 'G';
|
||||||
|
/// for msgs
|
||||||
|
pub const DC_PARAM_CMD_ARG4: char = 'H';
|
||||||
|
/// for msgs
|
||||||
|
pub const DC_PARAM_ERROR: char = 'L';
|
||||||
|
/// for msgs in PREPARING: space-separated list of message IDs of forwarded copies
|
||||||
|
pub const DC_PARAM_PREP_FORWARDS: char = 'P';
|
||||||
|
/// for msgs
|
||||||
|
pub const DC_PARAM_SET_LATITUDE: char = 'l';
|
||||||
|
/// for msgs
|
||||||
|
pub const DC_PARAM_SET_LONGITUDE: char = 'n';
|
||||||
|
|
||||||
|
/// for jobs
|
||||||
|
pub const DC_PARAM_SERVER_FOLDER: char = 'Z';
|
||||||
|
/// for jobs
|
||||||
|
pub const DC_PARAM_SERVER_UID: char = 'z';
|
||||||
|
/// for jobs
|
||||||
|
pub const DC_PARAM_ALSO_MOVE: char = 'M';
|
||||||
|
/// for jobs: space-separated list of message recipients
|
||||||
|
pub const DC_PARAM_RECIPIENTS: char = 'R';
|
||||||
|
/// for groups
|
||||||
|
pub const DC_PARAM_UNPROMOTED: char = 'U';
|
||||||
|
/// for groups and contacts
|
||||||
|
pub const DC_PARAM_PROFILE_IMAGE: char = 'i';
|
||||||
|
/// for chats
|
||||||
|
pub const DC_PARAM_SELFTALK: char = 'K';
|
||||||
|
|
||||||
|
// values for DC_PARAM_FORCE_PLAINTEXT
|
||||||
|
pub const DC_FP_ADD_AUTOCRYPT_HEADER: u8 = 1;
|
||||||
|
pub const DC_FP_NO_AUTOCRYPT_HEADER: u8 = 2;
|
||||||
|
|
||||||
|
/// An object for handling key=value parameter lists; for the key, curently only
|
||||||
|
/// a single character is allowed.
|
||||||
|
///
|
||||||
|
/// The object is used eg. by Chat or dc_msg_t, for readable paramter names,
|
||||||
|
/// these classes define some DC_PARAM_* constantats.
|
||||||
|
///
|
||||||
|
/// Only for library-internal use.
|
||||||
|
#[derive(Copy, Clone)]
|
||||||
|
#[repr(C)]
|
||||||
|
pub struct dc_param_t {
|
||||||
|
pub packed: *mut libc::c_char,
|
||||||
|
}
|
||||||
|
|
||||||
|
// values for DC_PARAM_FORCE_PLAINTEXT
|
||||||
|
/* user functions */
|
||||||
|
pub unsafe fn dc_param_exists(param: *mut dc_param_t, key: libc::c_int) -> libc::c_int {
|
||||||
|
let mut p2: *mut libc::c_char = 0 as *mut libc::c_char;
|
||||||
|
if param.is_null() || key == 0i32 {
|
||||||
|
return 0i32;
|
||||||
|
}
|
||||||
|
return if !find_param((*param).packed, key, &mut p2).is_null() {
|
||||||
|
1i32
|
||||||
|
} else {
|
||||||
|
0i32
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe extern "C" fn find_param(
|
||||||
|
haystack: *mut libc::c_char,
|
||||||
|
key: libc::c_int,
|
||||||
|
ret_p2: *mut *mut libc::c_char,
|
||||||
|
) -> *mut libc::c_char {
|
||||||
|
let mut p1: *mut libc::c_char;
|
||||||
|
let mut p2: *mut libc::c_char;
|
||||||
|
p1 = haystack;
|
||||||
|
loop {
|
||||||
|
if p1.is_null() || *p1 as libc::c_int == 0i32 {
|
||||||
|
return 0 as *mut libc::c_char;
|
||||||
|
} else {
|
||||||
|
if *p1 as libc::c_int == key && *p1.offset(1isize) as libc::c_int == '=' as i32 {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
p1 = strchr(p1, '\n' as i32);
|
||||||
|
if !p1.is_null() {
|
||||||
|
p1 = p1.offset(1isize)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
p2 = strchr(p1, '\n' as i32);
|
||||||
|
if p2.is_null() {
|
||||||
|
p2 = &mut *p1.offset(strlen(p1) as isize) as *mut libc::c_char
|
||||||
|
}
|
||||||
|
*ret_p2 = p2;
|
||||||
|
|
||||||
|
p1
|
||||||
|
}
|
||||||
|
|
||||||
|
/* the value may be an empty string, "def" is returned only if the value unset. The result must be free()'d in any case. */
|
||||||
|
pub unsafe fn dc_param_get(
|
||||||
|
param: *const dc_param_t,
|
||||||
|
key: libc::c_int,
|
||||||
|
def: *const libc::c_char,
|
||||||
|
) -> *mut libc::c_char {
|
||||||
|
let mut p1: *mut libc::c_char;
|
||||||
|
let mut p2: *mut libc::c_char = 0 as *mut libc::c_char;
|
||||||
|
let bak: libc::c_char;
|
||||||
|
let ret: *mut libc::c_char;
|
||||||
|
if param.is_null() || key == 0i32 {
|
||||||
|
return if !def.is_null() {
|
||||||
|
dc_strdup(def)
|
||||||
|
} else {
|
||||||
|
0 as *mut libc::c_char
|
||||||
|
};
|
||||||
|
}
|
||||||
|
p1 = find_param((*param).packed, key, &mut p2);
|
||||||
|
if p1.is_null() {
|
||||||
|
return if !def.is_null() {
|
||||||
|
dc_strdup(def)
|
||||||
|
} else {
|
||||||
|
0 as *mut libc::c_char
|
||||||
|
};
|
||||||
|
}
|
||||||
|
p1 = p1.offset(2isize);
|
||||||
|
bak = *p2;
|
||||||
|
*p2 = 0i32 as libc::c_char;
|
||||||
|
ret = dc_strdup(p1);
|
||||||
|
dc_rtrim(ret);
|
||||||
|
*p2 = bak;
|
||||||
|
|
||||||
|
ret
|
||||||
|
}
|
||||||
|
|
||||||
|
pub unsafe fn dc_param_get_int(
|
||||||
|
param: *const dc_param_t,
|
||||||
|
key: libc::c_int,
|
||||||
|
def: int32_t,
|
||||||
|
) -> int32_t {
|
||||||
|
if param.is_null() || key == 0i32 {
|
||||||
|
return def;
|
||||||
|
}
|
||||||
|
let s = dc_param_get(param, key, 0 as *const libc::c_char);
|
||||||
|
if s.is_null() {
|
||||||
|
return def;
|
||||||
|
}
|
||||||
|
let ret = as_str(s).parse().unwrap_or_default();
|
||||||
|
free(s as *mut libc::c_void);
|
||||||
|
|
||||||
|
ret
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get value of a parameter.
|
||||||
|
*
|
||||||
|
* @memberof dc_param_t
|
||||||
|
* @param param Parameter object to query.
|
||||||
|
* @param key Key of the parameter to get, one of the DC_PARAM_* constants.
|
||||||
|
* @param def Value to return if the parameter is not set.
|
||||||
|
* @return The stored value or the default value.
|
||||||
|
*/
|
||||||
|
pub unsafe fn dc_param_get_float(
|
||||||
|
param: *const dc_param_t,
|
||||||
|
key: libc::c_int,
|
||||||
|
def: libc::c_double,
|
||||||
|
) -> libc::c_double {
|
||||||
|
if param.is_null() || key == 0 {
|
||||||
|
return def;
|
||||||
|
}
|
||||||
|
|
||||||
|
let str = dc_param_get(param, key, std::ptr::null());
|
||||||
|
if str.is_null() {
|
||||||
|
return def;
|
||||||
|
}
|
||||||
|
|
||||||
|
let ret = dc_atof(str) as libc::c_double;
|
||||||
|
free(str as *mut libc::c_void);
|
||||||
|
|
||||||
|
ret
|
||||||
|
}
|
||||||
|
|
||||||
|
pub unsafe fn dc_param_set(
|
||||||
|
mut param: *mut dc_param_t,
|
||||||
|
key: libc::c_int,
|
||||||
|
value: *const libc::c_char,
|
||||||
|
) {
|
||||||
|
let mut old1: *mut libc::c_char;
|
||||||
|
let mut old2: *mut libc::c_char;
|
||||||
|
let new1: *mut libc::c_char;
|
||||||
|
if param.is_null() || key == 0i32 {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
old1 = (*param).packed;
|
||||||
|
old2 = 0 as *mut libc::c_char;
|
||||||
|
if !old1.is_null() {
|
||||||
|
let p1: *mut libc::c_char;
|
||||||
|
let mut p2: *mut libc::c_char = 0 as *mut libc::c_char;
|
||||||
|
p1 = find_param(old1, key, &mut p2);
|
||||||
|
if !p1.is_null() {
|
||||||
|
*p1 = 0i32 as libc::c_char;
|
||||||
|
old2 = p2
|
||||||
|
} else if value.is_null() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dc_rtrim(old1);
|
||||||
|
dc_ltrim(old2);
|
||||||
|
if !old1.is_null() && *old1.offset(0isize) as libc::c_int == 0i32 {
|
||||||
|
old1 = 0 as *mut libc::c_char
|
||||||
|
}
|
||||||
|
if !old2.is_null() && *old2.offset(0isize) as libc::c_int == 0i32 {
|
||||||
|
old2 = 0 as *mut libc::c_char
|
||||||
|
}
|
||||||
|
if !value.is_null() {
|
||||||
|
new1 = dc_mprintf(
|
||||||
|
b"%s%s%c=%s%s%s\x00" as *const u8 as *const libc::c_char,
|
||||||
|
if !old1.is_null() {
|
||||||
|
old1
|
||||||
|
} else {
|
||||||
|
b"\x00" as *const u8 as *const libc::c_char
|
||||||
|
},
|
||||||
|
if !old1.is_null() {
|
||||||
|
b"\n\x00" as *const u8 as *const libc::c_char
|
||||||
|
} else {
|
||||||
|
b"\x00" as *const u8 as *const libc::c_char
|
||||||
|
},
|
||||||
|
key,
|
||||||
|
value,
|
||||||
|
if !old2.is_null() {
|
||||||
|
b"\n\x00" as *const u8 as *const libc::c_char
|
||||||
|
} else {
|
||||||
|
b"\x00" as *const u8 as *const libc::c_char
|
||||||
|
},
|
||||||
|
if !old2.is_null() {
|
||||||
|
old2
|
||||||
|
} else {
|
||||||
|
b"\x00" as *const u8 as *const libc::c_char
|
||||||
|
},
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
new1 = dc_mprintf(
|
||||||
|
b"%s%s%s\x00" as *const u8 as *const libc::c_char,
|
||||||
|
if !old1.is_null() {
|
||||||
|
old1
|
||||||
|
} else {
|
||||||
|
b"\x00" as *const u8 as *const libc::c_char
|
||||||
|
},
|
||||||
|
if !old1.is_null() && !old2.is_null() {
|
||||||
|
b"\n\x00" as *const u8 as *const libc::c_char
|
||||||
|
} else {
|
||||||
|
b"\x00" as *const u8 as *const libc::c_char
|
||||||
|
},
|
||||||
|
if !old2.is_null() {
|
||||||
|
old2
|
||||||
|
} else {
|
||||||
|
b"\x00" as *const u8 as *const libc::c_char
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
free((*param).packed as *mut libc::c_void);
|
||||||
|
(*param).packed = new1;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub unsafe fn dc_param_set_int(param: *mut dc_param_t, key: libc::c_int, value: int32_t) {
|
||||||
|
if param.is_null() || key == 0i32 {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let value_str: *mut libc::c_char = dc_mprintf(
|
||||||
|
b"%i\x00" as *const u8 as *const libc::c_char,
|
||||||
|
value as libc::c_int,
|
||||||
|
);
|
||||||
|
if value_str.is_null() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
dc_param_set(param, key, value_str);
|
||||||
|
free(value_str as *mut libc::c_void);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* library-private */
|
||||||
|
pub unsafe fn dc_param_new() -> *mut dc_param_t {
|
||||||
|
let mut param: *mut dc_param_t;
|
||||||
|
param = calloc(1, ::std::mem::size_of::<dc_param_t>()) as *mut dc_param_t;
|
||||||
|
assert!(!param.is_null());
|
||||||
|
(*param).packed = calloc(1, 1) as *mut libc::c_char;
|
||||||
|
|
||||||
|
param
|
||||||
|
}
|
||||||
|
|
||||||
|
pub unsafe fn dc_param_empty(param: *mut dc_param_t) {
|
||||||
|
if param.is_null() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
*(*param).packed.offset(0isize) = 0i32 as libc::c_char;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub unsafe fn dc_param_unref(param: *mut dc_param_t) {
|
||||||
|
if param.is_null() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
dc_param_empty(param);
|
||||||
|
free((*param).packed as *mut libc::c_void);
|
||||||
|
free(param as *mut libc::c_void);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub unsafe fn dc_param_set_packed(mut param: *mut dc_param_t, packed: *const libc::c_char) {
|
||||||
|
if param.is_null() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
dc_param_empty(param);
|
||||||
|
if !packed.is_null() {
|
||||||
|
free((*param).packed as *mut libc::c_void);
|
||||||
|
(*param).packed = dc_strdup(packed)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub unsafe fn dc_param_set_urlencoded(mut param: *mut dc_param_t, urlencoded: *const libc::c_char) {
|
||||||
|
if param.is_null() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
dc_param_empty(param);
|
||||||
|
if !urlencoded.is_null() {
|
||||||
|
free((*param).packed as *mut libc::c_void);
|
||||||
|
(*param).packed = dc_strdup(urlencoded);
|
||||||
|
dc_str_replace(
|
||||||
|
&mut (*param).packed,
|
||||||
|
b"&\x00" as *const u8 as *const libc::c_char,
|
||||||
|
b"\n\x00" as *const u8 as *const libc::c_char,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set parameter to a float.
|
||||||
|
*
|
||||||
|
* @memberof dc_param_t
|
||||||
|
* @param param Parameter object to modify.
|
||||||
|
* @param key Key of the parameter to modify, one of the DC_PARAM_* constants.
|
||||||
|
* @param value Value to store for key.
|
||||||
|
* @return None.
|
||||||
|
*/
|
||||||
|
pub unsafe fn dc_param_set_float(param: *mut dc_param_t, key: libc::c_int, value: libc::c_double) {
|
||||||
|
if param.is_null() || key == 0 {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let value_str = dc_ftoa(value);
|
||||||
|
if value_str.is_null() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
dc_param_set(param, key, value_str);
|
||||||
|
free(value_str as *mut libc::c_void);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use std::ffi::CStr;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_dc_param() {
|
||||||
|
unsafe {
|
||||||
|
let p1: *mut dc_param_t = dc_param_new();
|
||||||
|
dc_param_set_packed(
|
||||||
|
p1,
|
||||||
|
b"\r\n\r\na=1\nb=2\n\nc = 3 \x00" as *const u8 as *const libc::c_char,
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(dc_param_get_int(p1, 'a' as i32, 0), 1);
|
||||||
|
assert_eq!(dc_param_get_int(p1, 'b' as i32, 0), 2);
|
||||||
|
assert_eq!(dc_param_get_int(p1, 'c' as i32, 0), 0);
|
||||||
|
assert_eq!(dc_param_exists(p1, 'c' as i32), 0);
|
||||||
|
|
||||||
|
dc_param_set_int(p1, 'd' as i32, 4i32);
|
||||||
|
|
||||||
|
assert_eq!(dc_param_get_int(p1, 'd' as i32, 0), 4);
|
||||||
|
|
||||||
|
dc_param_empty(p1);
|
||||||
|
dc_param_set(
|
||||||
|
p1,
|
||||||
|
'a' as i32,
|
||||||
|
b"foo\x00" as *const u8 as *const libc::c_char,
|
||||||
|
);
|
||||||
|
dc_param_set_int(p1, 'b' as i32, 2i32);
|
||||||
|
dc_param_set(p1, 'c' as i32, 0 as *const libc::c_char);
|
||||||
|
dc_param_set_int(p1, 'd' as i32, 4i32);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
CStr::from_ptr((*p1).packed as *const libc::c_char)
|
||||||
|
.to_str()
|
||||||
|
.unwrap(),
|
||||||
|
"a=foo\nb=2\nd=4"
|
||||||
|
);
|
||||||
|
|
||||||
|
dc_param_set(p1, 'b' as i32, 0 as *const libc::c_char);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
CStr::from_ptr((*p1).packed as *const libc::c_char)
|
||||||
|
.to_str()
|
||||||
|
.unwrap(),
|
||||||
|
"a=foo\nd=4",
|
||||||
|
);
|
||||||
|
|
||||||
|
dc_param_set(p1, 'a' as i32, 0 as *const libc::c_char);
|
||||||
|
dc_param_set(p1, 'd' as i32, 0 as *const libc::c_char);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
CStr::from_ptr((*p1).packed as *const libc::c_char)
|
||||||
|
.to_str()
|
||||||
|
.unwrap(),
|
||||||
|
"",
|
||||||
|
);
|
||||||
|
|
||||||
|
dc_param_unref(p1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
346
src/dc_qr.rs
Normal file
346
src/dc_qr.rs
Normal file
@@ -0,0 +1,346 @@
|
|||||||
|
use crate::context::Context;
|
||||||
|
use crate::dc_chat::*;
|
||||||
|
use crate::dc_contact::*;
|
||||||
|
use crate::dc_log::*;
|
||||||
|
use crate::dc_lot::*;
|
||||||
|
use crate::dc_param::*;
|
||||||
|
use crate::dc_strencode::*;
|
||||||
|
use crate::dc_tools::*;
|
||||||
|
use crate::key::*;
|
||||||
|
use crate::peerstate::*;
|
||||||
|
use crate::types::*;
|
||||||
|
use crate::x::*;
|
||||||
|
|
||||||
|
// out-of-band verification
|
||||||
|
// id=contact
|
||||||
|
// text1=groupname
|
||||||
|
// id=contact
|
||||||
|
// id=contact
|
||||||
|
// test1=formatted fingerprint
|
||||||
|
// id=contact
|
||||||
|
// text1=text
|
||||||
|
// text1=URL
|
||||||
|
// text1=error string
|
||||||
|
pub unsafe fn dc_check_qr(context: &Context, qr: *const libc::c_char) -> *mut dc_lot_t {
|
||||||
|
let mut current_block: u64;
|
||||||
|
let mut payload: *mut libc::c_char = 0 as *mut libc::c_char;
|
||||||
|
// must be normalized, if set
|
||||||
|
let mut addr: *mut libc::c_char = 0 as *mut libc::c_char;
|
||||||
|
// must be normalized, if set
|
||||||
|
let mut fingerprint: *mut libc::c_char = 0 as *mut libc::c_char;
|
||||||
|
let mut name: *mut libc::c_char = 0 as *mut libc::c_char;
|
||||||
|
let mut invitenumber: *mut libc::c_char = 0 as *mut libc::c_char;
|
||||||
|
let mut auth: *mut libc::c_char = 0 as *mut libc::c_char;
|
||||||
|
let mut qr_parsed: *mut dc_lot_t = dc_lot_new();
|
||||||
|
let mut chat_id: uint32_t = 0i32 as uint32_t;
|
||||||
|
let mut device_msg: *mut libc::c_char = 0 as *mut libc::c_char;
|
||||||
|
let mut grpid: *mut libc::c_char = 0 as *mut libc::c_char;
|
||||||
|
let mut grpname: *mut libc::c_char = 0 as *mut libc::c_char;
|
||||||
|
(*qr_parsed).state = 0i32;
|
||||||
|
if !qr.is_null() {
|
||||||
|
dc_log_info(
|
||||||
|
context,
|
||||||
|
0i32,
|
||||||
|
b"Scanned QR code: %s\x00" as *const u8 as *const libc::c_char,
|
||||||
|
qr,
|
||||||
|
);
|
||||||
|
/* split parameters from the qr code
|
||||||
|
------------------------------------ */
|
||||||
|
if strncasecmp(
|
||||||
|
qr,
|
||||||
|
b"OPENPGP4FPR:\x00" as *const u8 as *const libc::c_char,
|
||||||
|
strlen(b"OPENPGP4FPR:\x00" as *const u8 as *const libc::c_char),
|
||||||
|
) == 0i32
|
||||||
|
{
|
||||||
|
payload =
|
||||||
|
dc_strdup(&*qr.offset(strlen(
|
||||||
|
b"OPENPGP4FPR:\x00" as *const u8 as *const libc::c_char,
|
||||||
|
) as isize));
|
||||||
|
let mut fragment: *mut libc::c_char = strchr(payload, '#' as i32);
|
||||||
|
if !fragment.is_null() {
|
||||||
|
*fragment = 0i32 as libc::c_char;
|
||||||
|
fragment = fragment.offset(1isize);
|
||||||
|
let param: *mut dc_param_t = dc_param_new();
|
||||||
|
dc_param_set_urlencoded(param, fragment);
|
||||||
|
addr = dc_param_get(param, 'a' as i32, 0 as *const libc::c_char);
|
||||||
|
if !addr.is_null() {
|
||||||
|
let mut urlencoded: *mut libc::c_char =
|
||||||
|
dc_param_get(param, 'n' as i32, 0 as *const libc::c_char);
|
||||||
|
if !urlencoded.is_null() {
|
||||||
|
name = dc_urldecode(urlencoded);
|
||||||
|
dc_normalize_name(name);
|
||||||
|
free(urlencoded as *mut libc::c_void);
|
||||||
|
}
|
||||||
|
invitenumber = dc_param_get(param, 'i' as i32, 0 as *const libc::c_char);
|
||||||
|
auth = dc_param_get(param, 's' as i32, 0 as *const libc::c_char);
|
||||||
|
grpid = dc_param_get(param, 'x' as i32, 0 as *const libc::c_char);
|
||||||
|
if !grpid.is_null() {
|
||||||
|
urlencoded = dc_param_get(param, 'g' as i32, 0 as *const libc::c_char);
|
||||||
|
if !urlencoded.is_null() {
|
||||||
|
grpname = dc_urldecode(urlencoded);
|
||||||
|
free(urlencoded as *mut libc::c_void);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dc_param_unref(param);
|
||||||
|
}
|
||||||
|
fingerprint = dc_normalize_fingerprint_c(payload);
|
||||||
|
current_block = 5023038348526654800;
|
||||||
|
} else if strncasecmp(
|
||||||
|
qr,
|
||||||
|
b"mailto:\x00" as *const u8 as *const libc::c_char,
|
||||||
|
strlen(b"mailto:\x00" as *const u8 as *const libc::c_char),
|
||||||
|
) == 0i32
|
||||||
|
{
|
||||||
|
payload = dc_strdup(
|
||||||
|
&*qr.offset(strlen(b"mailto:\x00" as *const u8 as *const libc::c_char) as isize),
|
||||||
|
);
|
||||||
|
let query: *mut libc::c_char = strchr(payload, '?' as i32);
|
||||||
|
if !query.is_null() {
|
||||||
|
*query = 0i32 as libc::c_char
|
||||||
|
}
|
||||||
|
addr = dc_strdup(payload);
|
||||||
|
current_block = 5023038348526654800;
|
||||||
|
} else if strncasecmp(
|
||||||
|
qr,
|
||||||
|
b"SMTP:\x00" as *const u8 as *const libc::c_char,
|
||||||
|
strlen(b"SMTP:\x00" as *const u8 as *const libc::c_char),
|
||||||
|
) == 0i32
|
||||||
|
{
|
||||||
|
payload = dc_strdup(
|
||||||
|
&*qr.offset(strlen(b"SMTP:\x00" as *const u8 as *const libc::c_char) as isize),
|
||||||
|
);
|
||||||
|
let colon: *mut libc::c_char = strchr(payload, ':' as i32);
|
||||||
|
if !colon.is_null() {
|
||||||
|
*colon = 0i32 as libc::c_char
|
||||||
|
}
|
||||||
|
addr = dc_strdup(payload);
|
||||||
|
current_block = 5023038348526654800;
|
||||||
|
} else if strncasecmp(
|
||||||
|
qr,
|
||||||
|
b"MATMSG:\x00" as *const u8 as *const libc::c_char,
|
||||||
|
strlen(b"MATMSG:\x00" as *const u8 as *const libc::c_char),
|
||||||
|
) == 0i32
|
||||||
|
{
|
||||||
|
/* scheme: `MATMSG:TO:addr...;SUB:subject...;BODY:body...;` - there may or may not be linebreaks after the fields */
|
||||||
|
/* does not work when the text `TO:` is used in subject/body _and_ TO: is not the first field. we ignore this case. */
|
||||||
|
let to: *mut libc::c_char = strstr(qr, b"TO:\x00" as *const u8 as *const libc::c_char);
|
||||||
|
if !to.is_null() {
|
||||||
|
addr = dc_strdup(&mut *to.offset(3isize));
|
||||||
|
let semicolon: *mut libc::c_char = strchr(addr, ';' as i32);
|
||||||
|
if !semicolon.is_null() {
|
||||||
|
*semicolon = 0i32 as libc::c_char
|
||||||
|
}
|
||||||
|
current_block = 5023038348526654800;
|
||||||
|
} else {
|
||||||
|
(*qr_parsed).state = 400i32;
|
||||||
|
(*qr_parsed).text1 =
|
||||||
|
dc_strdup(b"Bad e-mail address.\x00" as *const u8 as *const libc::c_char);
|
||||||
|
current_block = 16562876845594826114;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if strncasecmp(
|
||||||
|
qr,
|
||||||
|
b"BEGIN:VCARD\x00" as *const u8 as *const libc::c_char,
|
||||||
|
strlen(b"BEGIN:VCARD\x00" as *const u8 as *const libc::c_char),
|
||||||
|
) == 0i32
|
||||||
|
{
|
||||||
|
let lines: *mut carray = dc_split_into_lines(qr);
|
||||||
|
let mut i: libc::c_int = 0i32;
|
||||||
|
while (i as libc::c_uint) < carray_count(lines) {
|
||||||
|
let key: *mut libc::c_char =
|
||||||
|
carray_get(lines, i as libc::c_uint) as *mut libc::c_char;
|
||||||
|
dc_trim(key);
|
||||||
|
let mut value: *mut libc::c_char = strchr(key, ':' as i32);
|
||||||
|
if !value.is_null() {
|
||||||
|
*value = 0i32 as libc::c_char;
|
||||||
|
value = value.offset(1isize);
|
||||||
|
let mut semicolon_0: *mut libc::c_char = strchr(key, ';' as i32);
|
||||||
|
if !semicolon_0.is_null() {
|
||||||
|
*semicolon_0 = 0i32 as libc::c_char
|
||||||
|
}
|
||||||
|
if strcasecmp(key, b"EMAIL\x00" as *const u8 as *const libc::c_char) == 0i32
|
||||||
|
{
|
||||||
|
semicolon_0 = strchr(value, ';' as i32);
|
||||||
|
if !semicolon_0.is_null() {
|
||||||
|
*semicolon_0 = 0i32 as libc::c_char
|
||||||
|
}
|
||||||
|
addr = dc_strdup(value)
|
||||||
|
} else if strcasecmp(key, b"N\x00" as *const u8 as *const libc::c_char)
|
||||||
|
== 0i32
|
||||||
|
{
|
||||||
|
semicolon_0 = strchr(value, ';' as i32);
|
||||||
|
if !semicolon_0.is_null() {
|
||||||
|
semicolon_0 = strchr(semicolon_0.offset(1isize), ';' as i32);
|
||||||
|
if !semicolon_0.is_null() {
|
||||||
|
*semicolon_0 = 0i32 as libc::c_char
|
||||||
|
}
|
||||||
|
}
|
||||||
|
name = dc_strdup(value);
|
||||||
|
dc_str_replace(
|
||||||
|
&mut name,
|
||||||
|
b";\x00" as *const u8 as *const libc::c_char,
|
||||||
|
b",\x00" as *const u8 as *const libc::c_char,
|
||||||
|
);
|
||||||
|
dc_normalize_name(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
i += 1
|
||||||
|
}
|
||||||
|
dc_free_splitted_lines(lines);
|
||||||
|
}
|
||||||
|
current_block = 5023038348526654800;
|
||||||
|
}
|
||||||
|
match current_block {
|
||||||
|
16562876845594826114 => {}
|
||||||
|
_ => {
|
||||||
|
/* check the paramters
|
||||||
|
---------------------- */
|
||||||
|
if !addr.is_null() {
|
||||||
|
/* urldecoding is needed at least for OPENPGP4FPR but should not hurt in the other cases */
|
||||||
|
let mut temp: *mut libc::c_char = dc_urldecode(addr);
|
||||||
|
free(addr as *mut libc::c_void);
|
||||||
|
addr = temp;
|
||||||
|
temp = dc_addr_normalize(addr);
|
||||||
|
free(addr as *mut libc::c_void);
|
||||||
|
addr = temp;
|
||||||
|
if !dc_may_be_valid_addr(addr) {
|
||||||
|
(*qr_parsed).state = 400i32;
|
||||||
|
(*qr_parsed).text1 = dc_strdup(
|
||||||
|
b"Bad e-mail address.\x00" as *const u8 as *const libc::c_char,
|
||||||
|
);
|
||||||
|
current_block = 16562876845594826114;
|
||||||
|
} else {
|
||||||
|
current_block = 14116432890150942211;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
current_block = 14116432890150942211;
|
||||||
|
}
|
||||||
|
match current_block {
|
||||||
|
16562876845594826114 => {}
|
||||||
|
_ => {
|
||||||
|
if !fingerprint.is_null() {
|
||||||
|
if strlen(fingerprint) != 40 {
|
||||||
|
(*qr_parsed).state = 400i32;
|
||||||
|
(*qr_parsed).text1 = dc_strdup(
|
||||||
|
b"Bad fingerprint length in QR code.\x00" as *const u8
|
||||||
|
as *const libc::c_char,
|
||||||
|
);
|
||||||
|
current_block = 16562876845594826114;
|
||||||
|
} else {
|
||||||
|
current_block = 5409161009579131794;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
current_block = 5409161009579131794;
|
||||||
|
}
|
||||||
|
match current_block {
|
||||||
|
16562876845594826114 => {}
|
||||||
|
_ => {
|
||||||
|
if !fingerprint.is_null() {
|
||||||
|
let peerstate = Peerstate::from_fingerprint(
|
||||||
|
context,
|
||||||
|
&context.sql,
|
||||||
|
as_str(fingerprint),
|
||||||
|
);
|
||||||
|
if addr.is_null() || invitenumber.is_null() || auth.is_null() {
|
||||||
|
if let Some(peerstate) = peerstate {
|
||||||
|
(*qr_parsed).state = 210i32;
|
||||||
|
let c_addr = peerstate
|
||||||
|
.addr
|
||||||
|
.as_ref()
|
||||||
|
.map(to_cstring)
|
||||||
|
.unwrap_or_default();
|
||||||
|
let addr_ptr = if peerstate.addr.is_some() {
|
||||||
|
c_addr.as_ptr()
|
||||||
|
} else {
|
||||||
|
std::ptr::null()
|
||||||
|
};
|
||||||
|
(*qr_parsed).id = dc_add_or_lookup_contact(
|
||||||
|
context,
|
||||||
|
0 as *const libc::c_char,
|
||||||
|
addr_ptr,
|
||||||
|
0x80i32,
|
||||||
|
0 as *mut libc::c_int,
|
||||||
|
);
|
||||||
|
dc_create_or_lookup_nchat_by_contact_id(
|
||||||
|
context,
|
||||||
|
(*qr_parsed).id,
|
||||||
|
2i32,
|
||||||
|
&mut chat_id,
|
||||||
|
0 as *mut libc::c_int,
|
||||||
|
);
|
||||||
|
device_msg = dc_mprintf(
|
||||||
|
b"%s verified.\x00" as *const u8
|
||||||
|
as *const libc::c_char,
|
||||||
|
peerstate.addr,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
(*qr_parsed).text1 =
|
||||||
|
dc_format_fingerprint_c(fingerprint);
|
||||||
|
(*qr_parsed).state = 230i32
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if !grpid.is_null() && !grpname.is_null() {
|
||||||
|
(*qr_parsed).state = 202i32;
|
||||||
|
(*qr_parsed).text1 = dc_strdup(grpname);
|
||||||
|
(*qr_parsed).text2 = dc_strdup(grpid)
|
||||||
|
} else {
|
||||||
|
(*qr_parsed).state = 200i32
|
||||||
|
}
|
||||||
|
(*qr_parsed).id = dc_add_or_lookup_contact(
|
||||||
|
context,
|
||||||
|
name,
|
||||||
|
addr,
|
||||||
|
0x80i32,
|
||||||
|
0 as *mut libc::c_int,
|
||||||
|
);
|
||||||
|
(*qr_parsed).fingerprint = dc_strdup(fingerprint);
|
||||||
|
(*qr_parsed).invitenumber = dc_strdup(invitenumber);
|
||||||
|
(*qr_parsed).auth = dc_strdup(auth)
|
||||||
|
}
|
||||||
|
} else if !addr.is_null() {
|
||||||
|
(*qr_parsed).state = 320i32;
|
||||||
|
(*qr_parsed).id = dc_add_or_lookup_contact(
|
||||||
|
context,
|
||||||
|
name,
|
||||||
|
addr,
|
||||||
|
0x80i32,
|
||||||
|
0 as *mut libc::c_int,
|
||||||
|
)
|
||||||
|
} else if strstr(
|
||||||
|
qr,
|
||||||
|
b"http://\x00" as *const u8 as *const libc::c_char,
|
||||||
|
) == qr as *mut libc::c_char
|
||||||
|
|| strstr(
|
||||||
|
qr,
|
||||||
|
b"https://\x00" as *const u8 as *const libc::c_char,
|
||||||
|
) == qr as *mut libc::c_char
|
||||||
|
{
|
||||||
|
(*qr_parsed).state = 332i32;
|
||||||
|
(*qr_parsed).text1 = dc_strdup(qr)
|
||||||
|
} else {
|
||||||
|
(*qr_parsed).state = 330i32;
|
||||||
|
(*qr_parsed).text1 = dc_strdup(qr)
|
||||||
|
}
|
||||||
|
if !device_msg.is_null() {
|
||||||
|
dc_add_device_msg(context, chat_id, device_msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
free(addr as *mut libc::c_void);
|
||||||
|
free(fingerprint as *mut libc::c_void);
|
||||||
|
free(payload as *mut libc::c_void);
|
||||||
|
free(name as *mut libc::c_void);
|
||||||
|
free(invitenumber as *mut libc::c_void);
|
||||||
|
free(auth as *mut libc::c_void);
|
||||||
|
free(device_msg as *mut libc::c_void);
|
||||||
|
free(grpname as *mut libc::c_void);
|
||||||
|
free(grpid as *mut libc::c_void);
|
||||||
|
|
||||||
|
qr_parsed
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
1095
src/dc_saxparser.rs
Normal file
1095
src/dc_saxparser.rs
Normal file
File diff suppressed because it is too large
Load Diff
1059
src/dc_securejoin.rs
Normal file
1059
src/dc_securejoin.rs
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,277 +1,374 @@
|
|||||||
use crate::dc_dehtml::*;
|
use crate::dc_dehtml::*;
|
||||||
|
use crate::dc_tools::*;
|
||||||
|
use crate::types::*;
|
||||||
|
use crate::x::*;
|
||||||
|
|
||||||
#[derive(Copy, Clone)]
|
#[derive(Copy, Clone)]
|
||||||
pub struct Simplify {
|
#[repr(C)]
|
||||||
pub is_forwarded: bool,
|
pub struct dc_simplify_t {
|
||||||
|
pub is_forwarded: libc::c_int,
|
||||||
|
pub is_cut_at_begin: libc::c_int,
|
||||||
|
pub is_cut_at_end: libc::c_int,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return index of footer line in vector of message lines, or vector length if
|
pub unsafe fn dc_simplify_new() -> *mut dc_simplify_t {
|
||||||
/// no footer is found.
|
let simplify: *mut dc_simplify_t;
|
||||||
///
|
simplify = calloc(1, ::std::mem::size_of::<dc_simplify_t>()) as *mut dc_simplify_t;
|
||||||
/// Also return whether not-standard (rfc3676, §4.3) footer is found.
|
assert!(!simplify.is_null());
|
||||||
fn find_message_footer(lines: &[&str]) -> (usize, bool) {
|
|
||||||
for ix in 0..lines.len() {
|
|
||||||
let line = lines[ix];
|
|
||||||
|
|
||||||
// quoted-printable may encode `-- ` to `-- =20` which is converted
|
simplify
|
||||||
// back to `-- `
|
|
||||||
match line.as_ref() {
|
|
||||||
"-- " | "-- " => return (ix, false),
|
|
||||||
"--" | "---" | "----" => return (ix, true),
|
|
||||||
_ => (),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return (lines.len(), false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Simplify {
|
pub unsafe fn dc_simplify_unref(simplify: *mut dc_simplify_t) {
|
||||||
pub fn new() -> Self {
|
if simplify.is_null() {
|
||||||
Simplify {
|
return;
|
||||||
is_forwarded: false,
|
}
|
||||||
|
free(simplify as *mut libc::c_void);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Simplify and normalise text: Remove quotes, signatures, unnecessary
|
||||||
|
lineends etc.
|
||||||
|
The data returned from Simplify() must be free()'d when no longer used, private */
|
||||||
|
pub unsafe fn dc_simplify_simplify(
|
||||||
|
mut simplify: *mut dc_simplify_t,
|
||||||
|
in_unterminated: *const libc::c_char,
|
||||||
|
in_bytes: libc::c_int,
|
||||||
|
is_html: libc::c_int,
|
||||||
|
is_msgrmsg: libc::c_int,
|
||||||
|
) -> *mut libc::c_char {
|
||||||
|
/* create a copy of the given buffer */
|
||||||
|
let mut out: *mut libc::c_char;
|
||||||
|
let mut temp: *mut libc::c_char;
|
||||||
|
if simplify.is_null() || in_unterminated.is_null() || in_bytes <= 0i32 {
|
||||||
|
return dc_strdup(b"\x00" as *const u8 as *const libc::c_char);
|
||||||
|
}
|
||||||
|
(*simplify).is_forwarded = 0i32;
|
||||||
|
(*simplify).is_cut_at_begin = 0i32;
|
||||||
|
(*simplify).is_cut_at_end = 0i32;
|
||||||
|
out = strndup(
|
||||||
|
in_unterminated as *mut libc::c_char,
|
||||||
|
in_bytes as libc::c_ulong,
|
||||||
|
);
|
||||||
|
if out.is_null() {
|
||||||
|
return dc_strdup(b"\x00" as *const u8 as *const libc::c_char);
|
||||||
|
}
|
||||||
|
if 0 != is_html {
|
||||||
|
temp = dc_dehtml(out);
|
||||||
|
if !temp.is_null() {
|
||||||
|
free(out as *mut libc::c_void);
|
||||||
|
out = temp
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
dc_remove_cr_chars(out);
|
||||||
|
temp = dc_simplify_simplify_plain_text(simplify, out, is_msgrmsg);
|
||||||
|
if !temp.is_null() {
|
||||||
|
free(out as *mut libc::c_void);
|
||||||
|
out = temp
|
||||||
|
}
|
||||||
|
dc_remove_cr_chars(out);
|
||||||
|
|
||||||
/// Simplify and normalise text: Remove quotes, signatures, unnecessary
|
out
|
||||||
/// lineends etc.
|
}
|
||||||
/// The data returned from simplify() must be free()'d when no longer used.
|
|
||||||
pub fn simplify(&mut self, input: &str, is_html: bool, is_msgrmsg: bool) -> String {
|
/**
|
||||||
let mut out = if is_html {
|
* Simplify Plain Text
|
||||||
dc_dehtml(input)
|
*/
|
||||||
|
unsafe fn dc_simplify_simplify_plain_text(
|
||||||
|
mut simplify: *mut dc_simplify_t,
|
||||||
|
buf_terminated: *const libc::c_char,
|
||||||
|
is_msgrmsg: libc::c_int,
|
||||||
|
) -> *mut libc::c_char {
|
||||||
|
/* This function ...
|
||||||
|
... removes all text after the line `-- ` (footer mark)
|
||||||
|
... removes full quotes at the beginning and at the end of the text -
|
||||||
|
these are all lines starting with the character `>`
|
||||||
|
... remove a non-empty line before the removed quote (contains sth. like "On 2.9.2016, Bjoern wrote:" in different formats and lanugages) */
|
||||||
|
/* split the given buffer into lines */
|
||||||
|
let lines: *mut carray = dc_split_into_lines(buf_terminated);
|
||||||
|
let mut l: libc::c_int;
|
||||||
|
let mut l_first: libc::c_int = 0i32;
|
||||||
|
/* if l_last is -1, there are no lines */
|
||||||
|
let mut l_last: libc::c_int =
|
||||||
|
carray_count(lines).wrapping_sub(1i32 as libc::c_uint) as libc::c_int;
|
||||||
|
let mut line: *mut libc::c_char;
|
||||||
|
let mut footer_mark: libc::c_int = 0i32;
|
||||||
|
l = l_first;
|
||||||
|
while l <= l_last {
|
||||||
|
line = carray_get(lines, l as libc::c_uint) as *mut libc::c_char;
|
||||||
|
if strcmp(line, b"-- \x00" as *const u8 as *const libc::c_char) == 0i32
|
||||||
|
|| strcmp(line, b"-- \x00" as *const u8 as *const libc::c_char) == 0i32
|
||||||
|
{
|
||||||
|
footer_mark = 1i32
|
||||||
|
}
|
||||||
|
if strcmp(line, b"--\x00" as *const u8 as *const libc::c_char) == 0i32
|
||||||
|
|| strcmp(line, b"---\x00" as *const u8 as *const libc::c_char) == 0i32
|
||||||
|
|| strcmp(line, b"----\x00" as *const u8 as *const libc::c_char) == 0i32
|
||||||
|
{
|
||||||
|
footer_mark = 1i32;
|
||||||
|
(*simplify).is_cut_at_end = 1i32
|
||||||
|
}
|
||||||
|
if 0 != footer_mark {
|
||||||
|
l_last = l - 1i32;
|
||||||
|
/* done */
|
||||||
|
break;
|
||||||
} else {
|
} else {
|
||||||
input.to_string()
|
l += 1
|
||||||
};
|
|
||||||
|
|
||||||
out.retain(|c| c != '\r');
|
|
||||||
out = self.simplify_plain_text(&out, is_msgrmsg);
|
|
||||||
out.retain(|c| c != '\r');
|
|
||||||
|
|
||||||
out
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Simplify Plain Text
|
|
||||||
*/
|
|
||||||
#[allow(non_snake_case)]
|
|
||||||
fn simplify_plain_text(&mut self, buf_terminated: &str, is_msgrmsg: bool) -> String {
|
|
||||||
/* This function ...
|
|
||||||
... removes all text after the line `-- ` (footer mark)
|
|
||||||
... removes full quotes at the beginning and at the end of the text -
|
|
||||||
these are all lines starting with the character `>`
|
|
||||||
... remove a non-empty line before the removed quote (contains sth. like "On 2.9.2016, Bjoern wrote:" in different formats and lanugages) */
|
|
||||||
/* split the given buffer into lines */
|
|
||||||
let lines: Vec<_> = buf_terminated.split('\n').collect();
|
|
||||||
let mut l_first: usize = 0;
|
|
||||||
let mut is_cut_at_begin = false;
|
|
||||||
let (mut l_last, mut is_cut_at_end) = find_message_footer(&lines);
|
|
||||||
|
|
||||||
if l_last > l_first + 2 {
|
|
||||||
let line0 = lines[l_first];
|
|
||||||
let line1 = lines[l_first + 1];
|
|
||||||
let line2 = lines[l_first + 2];
|
|
||||||
if line0 == "---------- Forwarded message ----------"
|
|
||||||
&& line1.starts_with("From: ")
|
|
||||||
&& line2.is_empty()
|
|
||||||
{
|
|
||||||
self.is_forwarded = true;
|
|
||||||
l_first += 3
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
for l in l_first..l_last {
|
}
|
||||||
let line = lines[l];
|
if l_last - l_first + 1i32 >= 3i32 {
|
||||||
if line == "-----"
|
let line0: *mut libc::c_char =
|
||||||
|| line == "_____"
|
carray_get(lines, l_first as libc::c_uint) as *mut libc::c_char;
|
||||||
|| line == "====="
|
let line1: *mut libc::c_char =
|
||||||
|| line == "*****"
|
carray_get(lines, (l_first + 1i32) as libc::c_uint) as *mut libc::c_char;
|
||||||
|| line == "~~~~~"
|
let line2: *mut libc::c_char =
|
||||||
{
|
carray_get(lines, (l_first + 2i32) as libc::c_uint) as *mut libc::c_char;
|
||||||
l_last = l;
|
if strcmp(
|
||||||
is_cut_at_end = true;
|
line0,
|
||||||
/* done */
|
b"---------- Forwarded message ----------\x00" as *const u8 as *const libc::c_char,
|
||||||
|
) == 0i32
|
||||||
|
&& strncmp(line1, b"From: \x00" as *const u8 as *const libc::c_char, 6) == 0i32
|
||||||
|
&& *line2.offset(0isize) as libc::c_int == 0i32
|
||||||
|
{
|
||||||
|
(*simplify).is_forwarded = 1i32;
|
||||||
|
l_first += 3i32
|
||||||
|
}
|
||||||
|
}
|
||||||
|
l = l_first;
|
||||||
|
while l <= l_last {
|
||||||
|
line = carray_get(lines, l as libc::c_uint) as *mut libc::c_char;
|
||||||
|
if strncmp(line, b"-----\x00" as *const u8 as *const libc::c_char, 5) == 0i32
|
||||||
|
|| strncmp(line, b"_____\x00" as *const u8 as *const libc::c_char, 5) == 0i32
|
||||||
|
|| strncmp(line, b"=====\x00" as *const u8 as *const libc::c_char, 5) == 0i32
|
||||||
|
|| strncmp(line, b"*****\x00" as *const u8 as *const libc::c_char, 5) == 0i32
|
||||||
|
|| strncmp(line, b"~~~~~\x00" as *const u8 as *const libc::c_char, 5) == 0i32
|
||||||
|
{
|
||||||
|
l_last = l - 1i32;
|
||||||
|
(*simplify).is_cut_at_end = 1i32;
|
||||||
|
/* done */
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
l += 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if 0 == is_msgrmsg {
|
||||||
|
let mut l_lastQuotedLine: libc::c_int = -1i32;
|
||||||
|
l = l_last;
|
||||||
|
while l >= l_first {
|
||||||
|
line = carray_get(lines, l as libc::c_uint) as *mut libc::c_char;
|
||||||
|
if is_plain_quote(line) {
|
||||||
|
l_lastQuotedLine = l
|
||||||
|
} else if !is_empty_line(line) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
l -= 1
|
||||||
}
|
}
|
||||||
if !is_msgrmsg {
|
if l_lastQuotedLine != -1i32 {
|
||||||
let mut l_lastQuotedLine = None;
|
l_last = l_lastQuotedLine - 1i32;
|
||||||
for l in (l_first..l_last).rev() {
|
(*simplify).is_cut_at_end = 1i32;
|
||||||
let line = lines[l];
|
if l_last > 0i32 {
|
||||||
if is_plain_quote(line) {
|
if is_empty_line(carray_get(lines, l_last as libc::c_uint) as *mut libc::c_char) {
|
||||||
l_lastQuotedLine = Some(l)
|
l_last -= 1
|
||||||
} else if !is_empty_line(line) {
|
}
|
||||||
|
}
|
||||||
|
if l_last > 0i32 {
|
||||||
|
line = carray_get(lines, l_last as libc::c_uint) as *mut libc::c_char;
|
||||||
|
if is_quoted_headline(line) {
|
||||||
|
l_last -= 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if 0 == is_msgrmsg {
|
||||||
|
let mut l_lastQuotedLine_0: libc::c_int = -1i32;
|
||||||
|
let mut hasQuotedHeadline: libc::c_int = 0i32;
|
||||||
|
l = l_first;
|
||||||
|
while l <= l_last {
|
||||||
|
line = carray_get(lines, l as libc::c_uint) as *mut libc::c_char;
|
||||||
|
if is_plain_quote(line) {
|
||||||
|
l_lastQuotedLine_0 = l
|
||||||
|
} else if !is_empty_line(line) {
|
||||||
|
if is_quoted_headline(line) && 0 == hasQuotedHeadline && l_lastQuotedLine_0 == -1i32
|
||||||
|
{
|
||||||
|
hasQuotedHeadline = 1i32
|
||||||
|
} else {
|
||||||
|
/* non-quoting line found */
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if let Some(last_quoted_line) = l_lastQuotedLine {
|
l += 1
|
||||||
l_last = last_quoted_line;
|
|
||||||
is_cut_at_end = true;
|
|
||||||
if l_last > 1 {
|
|
||||||
if is_empty_line(lines[l_last - 1]) {
|
|
||||||
l_last -= 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if l_last > 1 {
|
|
||||||
let line = lines[l_last - 1];
|
|
||||||
if is_quoted_headline(line) {
|
|
||||||
l_last -= 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if !is_msgrmsg {
|
if l_lastQuotedLine_0 != -1i32 {
|
||||||
let mut l_lastQuotedLine_0 = None;
|
l_first = l_lastQuotedLine_0 + 1i32;
|
||||||
let mut hasQuotedHeadline = 0;
|
(*simplify).is_cut_at_begin = 1i32
|
||||||
for l in l_first..l_last {
|
|
||||||
let line = lines[l];
|
|
||||||
if is_plain_quote(line) {
|
|
||||||
l_lastQuotedLine_0 = Some(l)
|
|
||||||
} else if !is_empty_line(line) {
|
|
||||||
if is_quoted_headline(line)
|
|
||||||
&& 0 == hasQuotedHeadline
|
|
||||||
&& l_lastQuotedLine_0.is_none()
|
|
||||||
{
|
|
||||||
hasQuotedHeadline = 1i32
|
|
||||||
} else {
|
|
||||||
/* non-quoting line found */
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if let Some(last_quoted_line) = l_lastQuotedLine_0 {
|
|
||||||
l_first = last_quoted_line + 1;
|
|
||||||
is_cut_at_begin = true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
/* re-create buffer from the remaining lines */
|
|
||||||
let mut ret = String::new();
|
|
||||||
if is_cut_at_begin {
|
|
||||||
ret += "[...]";
|
|
||||||
}
|
|
||||||
/* we write empty lines only in case and non-empty line follows */
|
|
||||||
let mut pending_linebreaks: libc::c_int = 0i32;
|
|
||||||
let mut content_lines_added: libc::c_int = 0i32;
|
|
||||||
for l in l_first..l_last {
|
|
||||||
let line = lines[l];
|
|
||||||
if is_empty_line(line) {
|
|
||||||
pending_linebreaks += 1
|
|
||||||
} else {
|
|
||||||
if 0 != content_lines_added {
|
|
||||||
if pending_linebreaks > 2i32 {
|
|
||||||
pending_linebreaks = 2i32
|
|
||||||
}
|
|
||||||
while 0 != pending_linebreaks {
|
|
||||||
ret += "\n";
|
|
||||||
pending_linebreaks -= 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// the incoming message might contain invalid UTF8
|
|
||||||
ret += line;
|
|
||||||
content_lines_added += 1;
|
|
||||||
pending_linebreaks = 1i32
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if is_cut_at_end && (!is_cut_at_begin || 0 != content_lines_added) {
|
|
||||||
ret += " [...]";
|
|
||||||
}
|
|
||||||
|
|
||||||
ret
|
|
||||||
}
|
}
|
||||||
|
/* re-create buffer from the remaining lines */
|
||||||
|
let mut ret = String::new();
|
||||||
|
if 0 != (*simplify).is_cut_at_begin {
|
||||||
|
ret += "[...]";
|
||||||
|
}
|
||||||
|
/* we write empty lines only in case and non-empty line follows */
|
||||||
|
let mut pending_linebreaks: libc::c_int = 0i32;
|
||||||
|
let mut content_lines_added: libc::c_int = 0i32;
|
||||||
|
l = l_first;
|
||||||
|
while l <= l_last {
|
||||||
|
line = carray_get(lines, l as libc::c_uint) as *mut libc::c_char;
|
||||||
|
if is_empty_line(line) {
|
||||||
|
pending_linebreaks += 1
|
||||||
|
} else {
|
||||||
|
if 0 != content_lines_added {
|
||||||
|
if pending_linebreaks > 2i32 {
|
||||||
|
pending_linebreaks = 2i32
|
||||||
|
}
|
||||||
|
while 0 != pending_linebreaks {
|
||||||
|
ret += "\n";
|
||||||
|
pending_linebreaks -= 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ret += &to_string(line);
|
||||||
|
content_lines_added += 1;
|
||||||
|
pending_linebreaks = 1i32
|
||||||
|
}
|
||||||
|
l += 1
|
||||||
|
}
|
||||||
|
if 0 != (*simplify).is_cut_at_end
|
||||||
|
&& (0 == (*simplify).is_cut_at_begin || 0 != content_lines_added)
|
||||||
|
{
|
||||||
|
ret += " [...]";
|
||||||
|
}
|
||||||
|
dc_free_splitted_lines(lines);
|
||||||
|
|
||||||
|
strdup(to_cstring(ret).as_ptr())
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tools
|
* Tools
|
||||||
*/
|
*/
|
||||||
fn is_empty_line(buf: &str) -> bool {
|
unsafe fn is_empty_line(buf: *const libc::c_char) -> bool {
|
||||||
// XXX: can it be simplified to buf.chars().all(|c| c.is_whitespace())?
|
/* force unsigned - otherwise the `> ' '` comparison will fail */
|
||||||
//
|
let mut p1: *const libc::c_uchar = buf as *const libc::c_uchar;
|
||||||
// Strictly speaking, it is not equivalent (^A is not whitespace, but less than ' '),
|
while 0 != *p1 {
|
||||||
// but having control sequences in email body?!
|
if *p1 as libc::c_int > ' ' as i32 {
|
||||||
//
|
|
||||||
// See discussion at: https://github.com/deltachat/deltachat-core-rust/pull/402#discussion_r317062392
|
|
||||||
for c in buf.chars() {
|
|
||||||
if c > ' ' {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
p1 = p1.offset(1isize)
|
||||||
}
|
}
|
||||||
|
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_quoted_headline(buf: &str) -> bool {
|
unsafe fn is_quoted_headline(buf: *const libc::c_char) -> bool {
|
||||||
/* This function may be called for the line _directly_ before a quote.
|
/* This function may be called for the line _directly_ before a quote.
|
||||||
The function checks if the line contains sth. like "On 01.02.2016, xy@z wrote:" in various languages.
|
The function checks if the line contains sth. like "On 01.02.2016, xy@z wrote:" in various languages.
|
||||||
- Currently, we simply check if the last character is a ':'.
|
- Currently, we simply check if the last character is a ':'.
|
||||||
- Checking for the existence of an email address may fail (headlines may show the user's name instead of the address) */
|
- Checking for the existance of an email address may fail (headlines may show the user's name instead of the address) */
|
||||||
|
let buf_len: libc::c_int = strlen(buf) as libc::c_int;
|
||||||
|
if buf_len > 80i32 {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if buf_len > 0i32 && *buf.offset((buf_len - 1i32) as isize) as libc::c_int == ':' as i32 {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
buf.len() <= 80 && buf.ends_with(':')
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_plain_quote(buf: &str) -> bool {
|
unsafe fn is_plain_quote(buf: *const libc::c_char) -> bool {
|
||||||
buf.starts_with(">")
|
if *buf.offset(0isize) as libc::c_int == '>' as i32 {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use proptest::prelude::*;
|
use std::ffi::CStr;
|
||||||
|
|
||||||
proptest! {
|
#[test]
|
||||||
#[test]
|
fn test_simplify_trim() {
|
||||||
// proptest does not support [[:graphical:][:space:]] regex.
|
unsafe {
|
||||||
fn test_simplify_plain_text_fuzzy(input in "[!-~\t \n]+") {
|
let simplify: *mut dc_simplify_t = dc_simplify_new();
|
||||||
let output = Simplify::new().simplify_plain_text(&input, true);
|
let html: *const libc::c_char =
|
||||||
assert!(output.split('\n').all(|s| s != "-- "));
|
b"\r\r\nline1<br>\r\n\r\n\r\rline2\n\r\x00" as *const u8 as *const libc::c_char;
|
||||||
|
let plain: *mut libc::c_char =
|
||||||
|
dc_simplify_simplify(simplify, html, strlen(html) as libc::c_int, 1, 0);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
CStr::from_ptr(plain as *const libc::c_char)
|
||||||
|
.to_str()
|
||||||
|
.unwrap(),
|
||||||
|
"line1\nline2",
|
||||||
|
);
|
||||||
|
|
||||||
|
free(plain as *mut libc::c_void);
|
||||||
|
dc_simplify_unref(simplify);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_simplify_trim() {
|
|
||||||
let mut simplify = Simplify::new();
|
|
||||||
let html = "\r\r\nline1<br>\r\n\r\n\r\rline2\n\r";
|
|
||||||
let plain = simplify.simplify(html, true, false);
|
|
||||||
|
|
||||||
assert_eq!(plain, "line1\nline2");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_simplify_parse_href() {
|
fn test_simplify_parse_href() {
|
||||||
let mut simplify = Simplify::new();
|
unsafe {
|
||||||
let html = "<a href=url>text</a";
|
let simplify: *mut dc_simplify_t = dc_simplify_new();
|
||||||
let plain = simplify.simplify(html, true, false);
|
let html: *const libc::c_char =
|
||||||
|
b"<a href=url>text</a\x00" as *const u8 as *const libc::c_char;
|
||||||
|
let plain: *mut libc::c_char =
|
||||||
|
dc_simplify_simplify(simplify, html, strlen(html) as libc::c_int, 1, 0);
|
||||||
|
|
||||||
assert_eq!(plain, "[text](url)");
|
assert_eq!(
|
||||||
|
CStr::from_ptr(plain as *const libc::c_char)
|
||||||
|
.to_str()
|
||||||
|
.unwrap(),
|
||||||
|
"[text](url)",
|
||||||
|
);
|
||||||
|
|
||||||
|
free(plain as *mut libc::c_void);
|
||||||
|
dc_simplify_unref(simplify);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_simplify_bold_text() {
|
fn test_simplify_bold_text() {
|
||||||
let mut simplify = Simplify::new();
|
unsafe {
|
||||||
let html = "<!DOCTYPE name [<!DOCTYPE ...>]><!-- comment -->text <b><?php echo ... ?>bold</b><![CDATA[<>]]>";
|
let simplify: *mut dc_simplify_t = dc_simplify_new();
|
||||||
let plain = simplify.simplify(html, true, false);
|
let html: *const libc::c_char =
|
||||||
|
b"<!DOCTYPE name [<!DOCTYPE ...>]><!-- comment -->text <b><?php echo ... ?>bold</b><![CDATA[<>]]>\x00"
|
||||||
|
as *const u8 as *const libc::c_char;
|
||||||
|
let plain: *mut libc::c_char =
|
||||||
|
dc_simplify_simplify(simplify, html, strlen(html) as libc::c_int, 1, 0);
|
||||||
|
|
||||||
assert_eq!(plain, "text *bold*<>");
|
assert_eq!(
|
||||||
|
CStr::from_ptr(plain as *const libc::c_char)
|
||||||
|
.to_str()
|
||||||
|
.unwrap(),
|
||||||
|
"text *bold*<>",
|
||||||
|
);
|
||||||
|
|
||||||
|
free(plain as *mut libc::c_void);
|
||||||
|
dc_simplify_unref(simplify);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_simplify_html_encoded() {
|
fn test_simplify_html_encoded() {
|
||||||
let mut simplify = Simplify::new();
|
unsafe {
|
||||||
let html =
|
let simplify: *mut dc_simplify_t = dc_simplify_new();
|
||||||
"<>"'& äÄöÖüÜß fooÆçÇ ♦‎‏‌&noent;‍";
|
let html: *const libc::c_char =
|
||||||
|
b"<>"'& äÄöÖüÜß fooÆçÇ ♦&noent;‎‏‌‍\x00"
|
||||||
|
as *const u8 as *const libc::c_char;
|
||||||
|
let plain: *mut libc::c_char =
|
||||||
|
dc_simplify_simplify(simplify, html, strlen(html) as libc::c_int, 1, 0);
|
||||||
|
|
||||||
let plain = simplify.simplify(html, true, false);
|
assert_eq!(
|
||||||
|
strcmp(plain,
|
||||||
|
b"<>\"\'& \xc3\xa4\xc3\x84\xc3\xb6\xc3\x96\xc3\xbc\xc3\x9c\xc3\x9f foo\xc3\x86\xc3\xa7\xc3\x87 \xe2\x99\xa6&noent;\x00"
|
||||||
|
as *const u8 as *const libc::c_char),
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
|
||||||
assert_eq!(
|
free(plain as *mut libc::c_void);
|
||||||
plain,
|
dc_simplify_unref(simplify);
|
||||||
"<>\"\'& äÄöÖüÜß fooÆçÇ \u{2666}\u{200e}\u{200f}\u{200c}&noent;\u{200d}"
|
}
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_simplify_utilities() {
|
|
||||||
assert!(is_empty_line(" \t"));
|
|
||||||
assert!(is_empty_line(""));
|
|
||||||
assert!(is_empty_line(" \r"));
|
|
||||||
assert!(!is_empty_line(" x"));
|
|
||||||
assert!(is_plain_quote("> hello world"));
|
|
||||||
assert!(is_plain_quote(">>"));
|
|
||||||
assert!(!is_plain_quote("Life is pain"));
|
|
||||||
assert!(!is_plain_quote(""));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
1605
src/dc_sqlite3.rs
Normal file
1605
src/dc_sqlite3.rs
Normal file
File diff suppressed because it is too large
Load Diff
338
src/dc_stock.rs
Normal file
338
src/dc_stock.rs
Normal file
@@ -0,0 +1,338 @@
|
|||||||
|
use crate::constants::Event;
|
||||||
|
use crate::context::Context;
|
||||||
|
use crate::dc_contact::*;
|
||||||
|
use crate::dc_tools::*;
|
||||||
|
use crate::types::*;
|
||||||
|
use crate::x::*;
|
||||||
|
|
||||||
|
/* Return the string with the given ID by calling DC_EVENT_GET_STRING.
|
||||||
|
The result must be free()'d! */
|
||||||
|
pub unsafe fn dc_stock_str(context: &Context, id: libc::c_int) -> *mut libc::c_char {
|
||||||
|
return get_string(context, id, 0i32);
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe fn get_string(context: &Context, id: libc::c_int, qty: libc::c_int) -> *mut libc::c_char {
|
||||||
|
let mut ret: *mut libc::c_char;
|
||||||
|
|
||||||
|
ret =
|
||||||
|
context.call_cb(Event::GET_STRING, id as uintptr_t, qty as uintptr_t) as *mut libc::c_char;
|
||||||
|
|
||||||
|
if ret.is_null() {
|
||||||
|
ret = default_string(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
ret
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Add translated strings that are used by the messager backend.
|
||||||
|
As the logging functions may use these strings, do not log any
|
||||||
|
errors from here. */
|
||||||
|
unsafe fn default_string(id: libc::c_int) -> *mut libc::c_char {
|
||||||
|
// TODO match on enum values /rtn
|
||||||
|
match id {
|
||||||
|
1 => {
|
||||||
|
return dc_strdup(b"No messages.\x00" as *const u8 as
|
||||||
|
*const libc::c_char)
|
||||||
|
}
|
||||||
|
2 => {
|
||||||
|
return dc_strdup(b"Me\x00" as *const u8 as *const libc::c_char)
|
||||||
|
}
|
||||||
|
3 => {
|
||||||
|
return dc_strdup(b"Draft\x00" as *const u8 as *const libc::c_char)
|
||||||
|
}
|
||||||
|
4 => {
|
||||||
|
return dc_strdup(b"%1$s member(s)\x00" as *const u8 as
|
||||||
|
*const libc::c_char)
|
||||||
|
}
|
||||||
|
6 => {
|
||||||
|
return dc_strdup(b"%1$s contact(s)\x00" as *const u8 as
|
||||||
|
*const libc::c_char)
|
||||||
|
}
|
||||||
|
7 => {
|
||||||
|
return dc_strdup(b"Voice message\x00" as *const u8 as
|
||||||
|
*const libc::c_char)
|
||||||
|
}
|
||||||
|
8 => {
|
||||||
|
return dc_strdup(b"Contact requests\x00" as *const u8 as
|
||||||
|
*const libc::c_char)
|
||||||
|
}
|
||||||
|
9 => {
|
||||||
|
return dc_strdup(b"Image\x00" as *const u8 as *const libc::c_char)
|
||||||
|
}
|
||||||
|
23 => {
|
||||||
|
return dc_strdup(b"GIF\x00" as *const u8 as *const libc::c_char)
|
||||||
|
}
|
||||||
|
10 => {
|
||||||
|
return dc_strdup(b"Video\x00" as *const u8 as *const libc::c_char)
|
||||||
|
}
|
||||||
|
11 => {
|
||||||
|
return dc_strdup(b"Audio\x00" as *const u8 as *const libc::c_char)
|
||||||
|
}
|
||||||
|
12 => {
|
||||||
|
return dc_strdup(b"File\x00" as *const u8 as *const libc::c_char)
|
||||||
|
}
|
||||||
|
66 => {
|
||||||
|
return dc_strdup(b"Location\x00" as *const u8 as
|
||||||
|
*const libc::c_char)
|
||||||
|
}
|
||||||
|
24 => {
|
||||||
|
return dc_strdup(b"Encrypted message\x00" as *const u8 as
|
||||||
|
*const libc::c_char)
|
||||||
|
}
|
||||||
|
13 => {
|
||||||
|
return dc_strdup(b"Sent with my Delta Chat Messenger: https://delta.chat\x00"
|
||||||
|
as *const u8 as *const libc::c_char)
|
||||||
|
}
|
||||||
|
14 => {
|
||||||
|
return dc_strdup(b"Hello, I\'ve just created the group \"%1$s\" for us.\x00"
|
||||||
|
as *const u8 as *const libc::c_char)
|
||||||
|
}
|
||||||
|
15 => {
|
||||||
|
return dc_strdup(b"Group name changed from \"%1$s\" to \"%2$s\".\x00"
|
||||||
|
as *const u8 as *const libc::c_char)
|
||||||
|
}
|
||||||
|
16 => {
|
||||||
|
return dc_strdup(b"Group image changed.\x00" as *const u8 as
|
||||||
|
*const libc::c_char)
|
||||||
|
}
|
||||||
|
17 => {
|
||||||
|
return dc_strdup(b"Member %1$s added.\x00" as *const u8 as
|
||||||
|
*const libc::c_char)
|
||||||
|
}
|
||||||
|
18 => {
|
||||||
|
return dc_strdup(b"Member %1$s removed.\x00" as *const u8 as
|
||||||
|
*const libc::c_char)
|
||||||
|
}
|
||||||
|
19 => {
|
||||||
|
return dc_strdup(b"Group left.\x00" as *const u8 as
|
||||||
|
*const libc::c_char)
|
||||||
|
}
|
||||||
|
64 => {
|
||||||
|
return dc_strdup(b"Location streaming enabled.\x00" as *const u8
|
||||||
|
as *const libc::c_char)
|
||||||
|
}
|
||||||
|
65 => {
|
||||||
|
return dc_strdup(b"Location streaming disabled.\x00" as *const u8
|
||||||
|
as *const libc::c_char)
|
||||||
|
}
|
||||||
|
62 => {
|
||||||
|
return dc_strdup(b"%1$s by %2$s.\x00" as *const u8 as
|
||||||
|
*const libc::c_char)
|
||||||
|
}
|
||||||
|
63 => {
|
||||||
|
return dc_strdup(b"%1$s by me.\x00" as *const u8 as
|
||||||
|
*const libc::c_char)
|
||||||
|
}
|
||||||
|
25 => {
|
||||||
|
return dc_strdup(b"End-to-end encryption available.\x00" as
|
||||||
|
*const u8 as *const libc::c_char)
|
||||||
|
}
|
||||||
|
27 => {
|
||||||
|
return dc_strdup(b"Transport-encryption.\x00" as *const u8 as
|
||||||
|
*const libc::c_char)
|
||||||
|
}
|
||||||
|
28 => {
|
||||||
|
return dc_strdup(b"No encryption.\x00" as *const u8 as
|
||||||
|
*const libc::c_char)
|
||||||
|
}
|
||||||
|
30 => {
|
||||||
|
return dc_strdup(b"Fingerprints\x00" as *const u8 as
|
||||||
|
*const libc::c_char)
|
||||||
|
}
|
||||||
|
31 => {
|
||||||
|
return dc_strdup(b"Return receipt\x00" as *const u8 as
|
||||||
|
*const libc::c_char)
|
||||||
|
}
|
||||||
|
32 => {
|
||||||
|
return dc_strdup(b"This is a return receipt for the message \"%1$s\".\x00"
|
||||||
|
as *const u8 as *const libc::c_char)
|
||||||
|
}
|
||||||
|
33 => {
|
||||||
|
return dc_strdup(b"Group image deleted.\x00" as *const u8 as
|
||||||
|
*const libc::c_char)
|
||||||
|
}
|
||||||
|
34 => {
|
||||||
|
return dc_strdup(b"End-to-end encryption preferred.\x00" as
|
||||||
|
*const u8 as *const libc::c_char)
|
||||||
|
}
|
||||||
|
35 => {
|
||||||
|
return dc_strdup(b"%1$s verified.\x00" as *const u8 as
|
||||||
|
*const libc::c_char)
|
||||||
|
}
|
||||||
|
36 => {
|
||||||
|
return dc_strdup(b"Cannot verifiy %1$s\x00" as *const u8 as
|
||||||
|
*const libc::c_char)
|
||||||
|
}
|
||||||
|
37 => {
|
||||||
|
return dc_strdup(b"Changed setup for %1$s\x00" as *const u8 as
|
||||||
|
*const libc::c_char)
|
||||||
|
}
|
||||||
|
40 => {
|
||||||
|
return dc_strdup(b"Archived chats\x00" as *const u8 as
|
||||||
|
*const libc::c_char)
|
||||||
|
}
|
||||||
|
41 => {
|
||||||
|
return dc_strdup(b"Starred messages\x00" as *const u8 as
|
||||||
|
*const libc::c_char)
|
||||||
|
}
|
||||||
|
42 => {
|
||||||
|
return dc_strdup(b"Autocrypt Setup Message\x00" as *const u8 as
|
||||||
|
*const libc::c_char)
|
||||||
|
}
|
||||||
|
43 => {
|
||||||
|
return dc_strdup(b"This is the Autocrypt Setup Message used to transfer your key between clients.\n\nTo decrypt and use your key, open the message in an Autocrypt-compliant client and enter the setup code presented on the generating device.\x00"
|
||||||
|
as *const u8 as *const libc::c_char)
|
||||||
|
}
|
||||||
|
50 => {
|
||||||
|
return dc_strdup(b"Messages I sent to myself\x00" as *const u8 as
|
||||||
|
*const libc::c_char)
|
||||||
|
}
|
||||||
|
29 => {
|
||||||
|
return dc_strdup(b"This message was encrypted for another setup.\x00"
|
||||||
|
as *const u8 as *const libc::c_char)
|
||||||
|
}
|
||||||
|
60 => {
|
||||||
|
return dc_strdup(b"Cannot login as %1$s.\x00" as *const u8 as
|
||||||
|
*const libc::c_char)
|
||||||
|
}
|
||||||
|
61 => {
|
||||||
|
return dc_strdup(b"Response from %1$s: %2$s\x00" as *const u8 as
|
||||||
|
*const libc::c_char)
|
||||||
|
}
|
||||||
|
_ => { }
|
||||||
|
}
|
||||||
|
|
||||||
|
dc_strdup(b"ErrStr\x00" as *const u8 as *const libc::c_char)
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Replaces the first `%1$s` in the given String-ID by the given value.
|
||||||
|
The result must be free()'d! */
|
||||||
|
pub unsafe fn dc_stock_str_repl_string(
|
||||||
|
context: &Context,
|
||||||
|
id: libc::c_int,
|
||||||
|
to_insert: *const libc::c_char,
|
||||||
|
) -> *mut libc::c_char {
|
||||||
|
let mut ret: *mut libc::c_char = get_string(context, id, 0i32);
|
||||||
|
dc_str_replace(
|
||||||
|
&mut ret,
|
||||||
|
b"%1$s\x00" as *const u8 as *const libc::c_char,
|
||||||
|
to_insert,
|
||||||
|
);
|
||||||
|
dc_str_replace(
|
||||||
|
&mut ret,
|
||||||
|
b"%1$d\x00" as *const u8 as *const libc::c_char,
|
||||||
|
to_insert,
|
||||||
|
);
|
||||||
|
|
||||||
|
ret
|
||||||
|
}
|
||||||
|
|
||||||
|
pub unsafe fn dc_stock_str_repl_int(
|
||||||
|
context: &Context,
|
||||||
|
id: libc::c_int,
|
||||||
|
to_insert_int: libc::c_int,
|
||||||
|
) -> *mut libc::c_char {
|
||||||
|
let mut ret: *mut libc::c_char = get_string(context, id, to_insert_int);
|
||||||
|
let to_insert_str: *mut libc::c_char = dc_mprintf(
|
||||||
|
b"%i\x00" as *const u8 as *const libc::c_char,
|
||||||
|
to_insert_int as libc::c_int,
|
||||||
|
);
|
||||||
|
dc_str_replace(
|
||||||
|
&mut ret,
|
||||||
|
b"%1$s\x00" as *const u8 as *const libc::c_char,
|
||||||
|
to_insert_str,
|
||||||
|
);
|
||||||
|
dc_str_replace(
|
||||||
|
&mut ret,
|
||||||
|
b"%1$d\x00" as *const u8 as *const libc::c_char,
|
||||||
|
to_insert_str,
|
||||||
|
);
|
||||||
|
free(to_insert_str as *mut libc::c_void);
|
||||||
|
|
||||||
|
ret
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Replaces the first `%1$s` and `%2$s` in the given String-ID by the two given strings.
|
||||||
|
The result must be free()'d! */
|
||||||
|
pub unsafe fn dc_stock_str_repl_string2(
|
||||||
|
context: &Context,
|
||||||
|
id: libc::c_int,
|
||||||
|
to_insert: *const libc::c_char,
|
||||||
|
to_insert2: *const libc::c_char,
|
||||||
|
) -> *mut libc::c_char {
|
||||||
|
let mut ret: *mut libc::c_char = get_string(context, id, 0i32);
|
||||||
|
dc_str_replace(
|
||||||
|
&mut ret,
|
||||||
|
b"%1$s\x00" as *const u8 as *const libc::c_char,
|
||||||
|
to_insert,
|
||||||
|
);
|
||||||
|
dc_str_replace(
|
||||||
|
&mut ret,
|
||||||
|
b"%1$d\x00" as *const u8 as *const libc::c_char,
|
||||||
|
to_insert,
|
||||||
|
);
|
||||||
|
dc_str_replace(
|
||||||
|
&mut ret,
|
||||||
|
b"%2$s\x00" as *const u8 as *const libc::c_char,
|
||||||
|
to_insert2,
|
||||||
|
);
|
||||||
|
dc_str_replace(
|
||||||
|
&mut ret,
|
||||||
|
b"%2$d\x00" as *const u8 as *const libc::c_char,
|
||||||
|
to_insert2,
|
||||||
|
);
|
||||||
|
|
||||||
|
ret
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Misc. */
|
||||||
|
pub unsafe fn dc_stock_system_msg(
|
||||||
|
context: &Context,
|
||||||
|
str_id: libc::c_int,
|
||||||
|
mut param1: *const libc::c_char,
|
||||||
|
param2: *const libc::c_char,
|
||||||
|
from_id: uint32_t,
|
||||||
|
) -> *mut libc::c_char {
|
||||||
|
let ret: *mut libc::c_char;
|
||||||
|
let mut mod_contact: *mut dc_contact_t = 0 as *mut dc_contact_t;
|
||||||
|
let mut mod_displayname: *mut libc::c_char = 0 as *mut libc::c_char;
|
||||||
|
let mut from_contact: *mut dc_contact_t = 0 as *mut dc_contact_t;
|
||||||
|
let mut from_displayname: *mut libc::c_char = 0 as *mut libc::c_char;
|
||||||
|
if str_id == 17i32 || str_id == 18i32 {
|
||||||
|
let mod_contact_id: uint32_t = dc_lookup_contact_id_by_addr(context, param1);
|
||||||
|
if mod_contact_id != 0i32 as libc::c_uint {
|
||||||
|
mod_contact = dc_get_contact(context, mod_contact_id);
|
||||||
|
mod_displayname = dc_contact_get_name_n_addr(mod_contact);
|
||||||
|
param1 = mod_displayname
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let action: *mut libc::c_char = dc_stock_str_repl_string2(context, str_id, param1, param2);
|
||||||
|
if 0 != from_id {
|
||||||
|
if 0 != strlen(action)
|
||||||
|
&& *action.offset(strlen(action).wrapping_sub(1) as isize) as libc::c_int == '.' as i32
|
||||||
|
{
|
||||||
|
*action.offset(strlen(action).wrapping_sub(1) as isize) = 0i32 as libc::c_char
|
||||||
|
}
|
||||||
|
from_contact = dc_get_contact(context, from_id);
|
||||||
|
from_displayname = dc_contact_get_display_name(from_contact);
|
||||||
|
ret = dc_stock_str_repl_string2(
|
||||||
|
context,
|
||||||
|
if from_id == 1i32 as libc::c_uint {
|
||||||
|
63i32
|
||||||
|
} else {
|
||||||
|
62i32
|
||||||
|
},
|
||||||
|
action,
|
||||||
|
from_displayname,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
ret = dc_strdup(action)
|
||||||
|
}
|
||||||
|
free(action as *mut libc::c_void);
|
||||||
|
free(from_displayname as *mut libc::c_void);
|
||||||
|
free(mod_displayname as *mut libc::c_void);
|
||||||
|
dc_contact_unref(from_contact);
|
||||||
|
dc_contact_unref(mod_contact);
|
||||||
|
|
||||||
|
ret
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
76
src/dc_token.rs
Normal file
76
src/dc_token.rs
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
use crate::context::Context;
|
||||||
|
use crate::dc_sqlite3::*;
|
||||||
|
use crate::dc_tools::*;
|
||||||
|
use crate::types::*;
|
||||||
|
|
||||||
|
// Token namespaces
|
||||||
|
pub type dc_tokennamespc_t = libc::c_uint;
|
||||||
|
pub const DC_TOKEN_AUTH: dc_tokennamespc_t = 110;
|
||||||
|
pub const DC_TOKEN_INVITENUMBER: dc_tokennamespc_t = 100;
|
||||||
|
// Functions to read/write token from/to the database. A token is any string associated with a key.
|
||||||
|
pub unsafe fn dc_token_save(
|
||||||
|
context: &Context,
|
||||||
|
namespc: dc_tokennamespc_t,
|
||||||
|
foreign_id: uint32_t,
|
||||||
|
token: *const libc::c_char,
|
||||||
|
) {
|
||||||
|
let mut stmt: *mut sqlite3_stmt = 0 as *mut sqlite3_stmt;
|
||||||
|
if !token.is_null() {
|
||||||
|
// foreign_id may be 0
|
||||||
|
stmt = dc_sqlite3_prepare(
|
||||||
|
context,
|
||||||
|
&context.sql,
|
||||||
|
b"INSERT INTO tokens (namespc, foreign_id, token, timestamp) VALUES (?, ?, ?, ?);\x00"
|
||||||
|
as *const u8 as *const libc::c_char,
|
||||||
|
);
|
||||||
|
sqlite3_bind_int(stmt, 1i32, namespc as libc::c_int);
|
||||||
|
sqlite3_bind_int(stmt, 2i32, foreign_id as libc::c_int);
|
||||||
|
sqlite3_bind_text(stmt, 3i32, token, -1i32, None);
|
||||||
|
sqlite3_bind_int64(stmt, 4i32, time() as sqlite3_int64);
|
||||||
|
sqlite3_step(stmt);
|
||||||
|
}
|
||||||
|
sqlite3_finalize(stmt);
|
||||||
|
}
|
||||||
|
pub unsafe fn dc_token_lookup(
|
||||||
|
context: &Context,
|
||||||
|
namespc: dc_tokennamespc_t,
|
||||||
|
foreign_id: uint32_t,
|
||||||
|
) -> *mut libc::c_char {
|
||||||
|
let token: *mut libc::c_char;
|
||||||
|
let stmt: *mut sqlite3_stmt;
|
||||||
|
stmt = dc_sqlite3_prepare(
|
||||||
|
context,
|
||||||
|
&context.sql,
|
||||||
|
b"SELECT token FROM tokens WHERE namespc=? AND foreign_id=?;\x00" as *const u8
|
||||||
|
as *const libc::c_char,
|
||||||
|
);
|
||||||
|
sqlite3_bind_int(stmt, 1i32, namespc as libc::c_int);
|
||||||
|
sqlite3_bind_int(stmt, 2i32, foreign_id as libc::c_int);
|
||||||
|
sqlite3_step(stmt);
|
||||||
|
token = dc_strdup_keep_null(sqlite3_column_text(stmt, 0i32) as *mut libc::c_char);
|
||||||
|
|
||||||
|
sqlite3_finalize(stmt);
|
||||||
|
token
|
||||||
|
}
|
||||||
|
|
||||||
|
pub unsafe fn dc_token_exists(
|
||||||
|
context: &Context,
|
||||||
|
namespc: dc_tokennamespc_t,
|
||||||
|
token: *const libc::c_char,
|
||||||
|
) -> libc::c_int {
|
||||||
|
let mut exists: libc::c_int = 0i32;
|
||||||
|
let mut stmt: *mut sqlite3_stmt = 0 as *mut sqlite3_stmt;
|
||||||
|
if !token.is_null() {
|
||||||
|
stmt = dc_sqlite3_prepare(
|
||||||
|
context,
|
||||||
|
&context.sql,
|
||||||
|
b"SELECT id FROM tokens WHERE namespc=? AND token=?;\x00" as *const u8
|
||||||
|
as *const libc::c_char,
|
||||||
|
);
|
||||||
|
sqlite3_bind_int(stmt, 1i32, namespc as libc::c_int);
|
||||||
|
sqlite3_bind_text(stmt, 2i32, token, -1i32, None);
|
||||||
|
exists = (sqlite3_step(stmt) != 0i32) as libc::c_int
|
||||||
|
}
|
||||||
|
sqlite3_finalize(stmt);
|
||||||
|
return exists;
|
||||||
|
}
|
||||||
2029
src/dc_tools.rs
2029
src/dc_tools.rs
File diff suppressed because it is too large
Load Diff
1163
src/e2ee.rs
1163
src/e2ee.rs
File diff suppressed because it is too large
Load Diff
143
src/error.rs
143
src/error.rs
@@ -1,143 +0,0 @@
|
|||||||
use failure::Fail;
|
|
||||||
|
|
||||||
#[derive(Debug, Fail)]
|
|
||||||
pub enum Error {
|
|
||||||
#[fail(display = "Sqlite Error: {:?}", _0)]
|
|
||||||
Sql(rusqlite::Error),
|
|
||||||
#[fail(display = "Sqlite Connection Pool Error: {:?}", _0)]
|
|
||||||
ConnectionPool(r2d2::Error),
|
|
||||||
#[fail(display = "{:?}", _0)]
|
|
||||||
Failure(failure::Error),
|
|
||||||
#[fail(display = "Sqlite: Connection closed")]
|
|
||||||
SqlNoConnection,
|
|
||||||
#[fail(display = "Sqlite: Already open")]
|
|
||||||
SqlAlreadyOpen,
|
|
||||||
#[fail(display = "Sqlite: Failed to open")]
|
|
||||||
SqlFailedToOpen,
|
|
||||||
#[fail(display = "{:?}", _0)]
|
|
||||||
Io(std::io::Error),
|
|
||||||
#[fail(display = "{:?}", _0)]
|
|
||||||
Message(String),
|
|
||||||
#[fail(display = "{:?}", _0)]
|
|
||||||
Image(image_meta::ImageError),
|
|
||||||
#[fail(display = "{:?}", _0)]
|
|
||||||
Utf8(std::str::Utf8Error),
|
|
||||||
#[fail(display = "{:?}", _0)]
|
|
||||||
CStringError(crate::dc_tools::CStringError),
|
|
||||||
#[fail(display = "PGP: {:?}", _0)]
|
|
||||||
Pgp(pgp::errors::Error),
|
|
||||||
}
|
|
||||||
|
|
||||||
pub type Result<T> = std::result::Result<T, Error>;
|
|
||||||
|
|
||||||
impl From<rusqlite::Error> for Error {
|
|
||||||
fn from(err: rusqlite::Error) -> Error {
|
|
||||||
Error::Sql(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<failure::Error> for Error {
|
|
||||||
fn from(err: failure::Error) -> Error {
|
|
||||||
Error::Failure(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<r2d2::Error> for Error {
|
|
||||||
fn from(err: r2d2::Error) -> Error {
|
|
||||||
Error::ConnectionPool(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<std::io::Error> for Error {
|
|
||||||
fn from(err: std::io::Error) -> Error {
|
|
||||||
Error::Io(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<std::str::Utf8Error> for Error {
|
|
||||||
fn from(err: std::str::Utf8Error) -> Error {
|
|
||||||
Error::Utf8(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<image_meta::ImageError> for Error {
|
|
||||||
fn from(err: image_meta::ImageError) -> Error {
|
|
||||||
Error::Image(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<crate::dc_tools::CStringError> for Error {
|
|
||||||
fn from(err: crate::dc_tools::CStringError) -> Error {
|
|
||||||
Error::CStringError(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<pgp::errors::Error> for Error {
|
|
||||||
fn from(err: pgp::errors::Error) -> Error {
|
|
||||||
Error::Pgp(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[macro_export]
|
|
||||||
macro_rules! bail {
|
|
||||||
($e:expr) => {
|
|
||||||
return Err($crate::error::Error::Message($e.to_string()));
|
|
||||||
};
|
|
||||||
($fmt:expr, $($arg:tt)+) => {
|
|
||||||
return Err($crate::error::Error::Message(format!($fmt, $($arg)+)));
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
#[macro_export]
|
|
||||||
macro_rules! format_err {
|
|
||||||
($e:expr) => {
|
|
||||||
$crate::error::Error::Message($e.to_string());
|
|
||||||
};
|
|
||||||
($fmt:expr, $($arg:tt)+) => {
|
|
||||||
$crate::error::Error::Message(format!($fmt, $($arg)+));
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
#[macro_export(local_inner_macros)]
|
|
||||||
macro_rules! ensure {
|
|
||||||
($cond:expr, $e:expr) => {
|
|
||||||
if !($cond) {
|
|
||||||
bail!($e);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
($cond:expr, $fmt:expr, $($arg:tt)+) => {
|
|
||||||
if !($cond) {
|
|
||||||
bail!($fmt, $($arg)+);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
#[macro_export]
|
|
||||||
macro_rules! ensure_eq {
|
|
||||||
($left:expr, $right:expr) => ({
|
|
||||||
match (&$left, &$right) {
|
|
||||||
(left_val, right_val) => {
|
|
||||||
if !(*left_val == *right_val) {
|
|
||||||
bail!(r#"assertion failed: `(left == right)`
|
|
||||||
left: `{:?}`,
|
|
||||||
right: `{:?}`"#, left_val, right_val)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
($left:expr, $right:expr,) => ({
|
|
||||||
ensure_eq!($left, $right)
|
|
||||||
});
|
|
||||||
($left:expr, $right:expr, $($arg:tt)+) => ({
|
|
||||||
match (&($left), &($right)) {
|
|
||||||
(left_val, right_val) => {
|
|
||||||
if !(*left_val == *right_val) {
|
|
||||||
bail!(r#"assertion failed: `(left == right)`
|
|
||||||
left: `{:?}`,
|
|
||||||
right: `{:?}`: {}"#, left_val, right_val,
|
|
||||||
format_args!($($arg)+))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user