Compare commits
54 Commits
2a565b3ee7
...
main
Author | SHA1 | Date | |
---|---|---|---|
7707242d10 | |||
bd76603c54 | |||
9e764d72a1 | |||
b9faad742b | |||
370f5076a1 | |||
373311e826 | |||
67a8715a25 | |||
c773146b26 | |||
7b1b8bdc83 | |||
d06badfc96 | |||
8dbd023718 | |||
7cee6348fd | |||
14aa639a4b | |||
efe97a33c4 | |||
d77d04e9b1 | |||
db7ecc6d98 | |||
15174ea03f | |||
aae7607c7f | |||
825f5d90ed | |||
a95be7ee4b | |||
72947bc99d | |||
b511dd873b | |||
667eb3b3f6 | |||
cb463336bc | |||
1e481d4c9a | |||
e5e06c55f9 | |||
576fec63cd | |||
782662b82f | |||
1eefce4f1c | |||
b814a9aab0 | |||
49362a6606 | |||
7003b56ce6 | |||
09afdf51a4 | |||
a202dd84e8 | |||
0f9dcf476b | |||
54a73c8219 | |||
011ee98340 | |||
4a501b2e07 | |||
9473a13b65 | |||
9564a16aa2 | |||
52f13a3589 | |||
64bbaccc3a | |||
53bce50978 | |||
411f34e6ce | |||
169249b716 | |||
e08ec6b476 | |||
640948241a | |||
33d522779a | |||
c0e62ba6de | |||
41309d9f39 | |||
5eea970cf7 | |||
b6fbcfeeb8 | |||
ef43c81955 | |||
ef1342f372 |
898
Cargo.lock
generated
898
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -9,8 +9,9 @@ license = "MIT"
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0"
|
||||
async-std = "1.6"
|
||||
dbus = "0.8"
|
||||
deltachat = { git = "https://github.com/deltachat/deltachat-core-rust", tag="1.31.0" }
|
||||
deltachat = { git = "https://github.com/deltachat/deltachat-core-rust", tag="1.34.0" }
|
||||
directories = "2.0"
|
||||
rand = "0.7"
|
||||
|
||||
|
152
README.md
152
README.md
@@ -28,13 +28,15 @@ Here's where we're at right now:
|
||||
|
||||
- [x] Connect to DBUS
|
||||
- [x] Advertise enough properties / interfaces to become visible in Empathy
|
||||
- [~] Connect to deltachat-core-rust
|
||||
- [x] Connect to deltachat-core-rust
|
||||
- [x] Set up an account via autoconfiguration
|
||||
- [x] Appear as online in Empathy
|
||||
- [x] Disconnect!
|
||||
- [ ] Set up an account manually
|
||||
- [ ] Contacts handling
|
||||
- [ ] Text messages
|
||||
- [~] Contacts handling
|
||||
- [x] Text messages
|
||||
- [ ] Multimedia messages
|
||||
- [ ] Setup messages
|
||||
- [~] Setup messages
|
||||
- [ ] Import/Export
|
||||
- [ ] Group chats
|
||||
- [ ] Geolocation messages
|
||||
@@ -50,9 +52,143 @@ learning exercise!
|
||||
|
||||
## How
|
||||
|
||||
### Compiling
|
||||
|
||||
This project is written in Rust, so you'll need a rust compiler to build it.
|
||||
[Rustup]() comes highly recommended. Deltachat is also written in Rust and it
|
||||
needs the `nightly` version, so follow the instructions for that.
|
||||
[Rustup](https://github.com/rust-lang/rustup) comes highly recommended.
|
||||
|
||||
There is a [`rust-toolchain`](rust-toolchain) file that I try to keep synced
|
||||
with the version of rust that
|
||||
[`deltachat-core-rust`](https://github.com/deltachat/deltachat-core-rust)
|
||||
uses.
|
||||
|
||||
|
||||
Once you have a working rust compiler, just:
|
||||
|
||||
```
|
||||
$ cargo build --release
|
||||
```
|
||||
|
||||
to get a `telepathy-padfoot binary. Drop the release flag to make it build fast.
|
||||
|
||||
|
||||
### Cross-compiling amd64 -> i386
|
||||
|
||||
If you need a 32-bit binary and you're on an am64 bit system, this seems to
|
||||
work, as long as you have 32-bit versions of `libdbus-1` and `libssl` installed.
|
||||
|
||||
On Debian, the full sequence looks like:
|
||||
|
||||
```
|
||||
$ dpkg --print-architecture
|
||||
amd64
|
||||
# dpkg --add-architecture i386
|
||||
$ dpkg --print-foreign-architectures
|
||||
i386
|
||||
# apt update
|
||||
# apt install libdbus-1-dev:i386 libssl-dev:i386
|
||||
$ rustup target install i686-unknown-linux-gnu
|
||||
$ PKG_CONFIG_ALLOW_CROSS=1 cargo build --target=i686-unknown-linux-gnu --release
|
||||
```
|
||||
|
||||
This creates a 32-bit executable at `target/i686-unknown-linux-gnu/release/telepathy-padfoot`
|
||||
|
||||
I don't have a 32-bit machine to test this on, but happy to take fixes for it.
|
||||
|
||||
### Cross-compiling amd64 -> aarch64
|
||||
|
||||
This is a handy thing to do for linux phones, most of which use telepathy. Rust
|
||||
is quite heavy to compile - it's a pain even on a pinebook pro, which is the
|
||||
same architecture. Setup on a Debian machine is quite simple:
|
||||
|
||||
```
|
||||
$ dpkg --print-architecture
|
||||
amd64
|
||||
# dpkg --add-architecture arm64
|
||||
$ dpkg --print-foreign-architectures
|
||||
arm64
|
||||
# apt update
|
||||
# apt install libdbus-1-dev:arm64 libssl-dev:arm64 gcc-aarch64-linux-gnu
|
||||
$ rustup target install aarch64-unknown-linux-gnu
|
||||
$ RUSTFLAGS="-C linker=aarch64-linux-gnu-gcc" PKG_CONFIG_ALLOW_CROSS=1 cargo build --target=aarch64-unknown-linux-gnu --release
|
||||
```
|
||||
|
||||
We have to specify the linker because of [this bug](https://github.com/rust-lang/cargo/issues/4133).
|
||||
|
||||
Note that this doesn't create a static binary, so you'll need to match versions
|
||||
for the shared libraries that are on the phone. In theory we can create static
|
||||
binaries with musl, but openssl makes it hard. If you get it working, tell me
|
||||
how!
|
||||
|
||||
UBTouch uses an ancient version of OpenSSL: 1.0.2g. KDE Neon does much better
|
||||
with 1.1.1, so is easier to compile against.
|
||||
|
||||
An alternative approach to using multiarch (as above) is to use `debootstrap`
|
||||
(or a similar tool) to get a sysroot containing libraries of all the right
|
||||
versions. E.g. You can then add `-C link-args=--sysroot=/path/to/sysroot` to
|
||||
`RUSTFLAGS` to use those libraries. Ufff. I've not got this working yet either.
|
||||
|
||||
...I'm compiling it directly on the phone. Not ideal. Add swap.
|
||||
|
||||
Compiling directly on the phone, using KDE Neon, I can get Padfoot running at
|
||||
the same time as [Spacebar](https://invent.kde.org/plasma-mobile/spacebar),
|
||||
which is a Telepathy client. I can see that Padfoot is checked for protocols,
|
||||
but I don't see a way to start a connection with it yet. Next step for this is
|
||||
to get Spacebar built and running locally, for a better debugging experience.
|
||||
|
||||
postmarketOS is more difficult. It's an `aarch64...musl` target. Rustup doesn't
|
||||
support this, and the `rustc` included in the repositories is stable, not
|
||||
nightly, so compiling directly on the phone is very difficult. Cross-compile is
|
||||
likely the way to go here, in the end, but I need to get one of the two tries
|
||||
above working first. Spacebar is available, but Empathy is not.
|
||||
|
||||
Phosh uses Chatty, which is based on libpurple, so doesn't work with Padfoot.
|
||||
|
||||
In the end, I tried Mobian. This is regular ordinary Debian Bullseye, plus a few
|
||||
Phosh packages. Installing Empathy and Padfoot together (Chatty is bundled but
|
||||
doesn't work), I have a working setup \o/ - although there are many warts, I can
|
||||
use Deltachat on Linux Mobile in at least one configuration.
|
||||
|
||||
I'll probably keep Mobian for a while though, it's exactly what I want in a
|
||||
mobile phone. Yes, I am peculiar.
|
||||
|
||||
### Installing
|
||||
|
||||
There is a `share/` directory in this project that contains a bunch of files.
|
||||
They should be placed into `/usr/share`, following the same layout. Then put
|
||||
the binary into `/usr/lib/telepathy/telepathy-padfoot`.
|
||||
|
||||
I should probably put this into the makefile.
|
||||
|
||||
### Running
|
||||
|
||||
D-Bus activation is not enabled yet, since it gets in the way of disaster-driven
|
||||
development. Just run the `telepathy-padfoot` binary as the same user that your
|
||||
chat client will be running as. It registers to the DBUS **session bus**, and
|
||||
will be picked up next time your chat client scans (which may need a restart).
|
||||
|
||||
### Setup messages
|
||||
|
||||
If you send an autocrypt setup message while a padfoot connection is up, it will
|
||||
notice it and open a channel asking you to reply with a message like:
|
||||
|
||||
```
|
||||
IMEX: <id> nnnn-nnnn-nnnn-nnnn-nnnn-nnnn-nnnn-nnnn-nnnn
|
||||
```
|
||||
|
||||
The ID is the delta-generated message ID, while the rest is the setup code. No
|
||||
whitespace!
|
||||
|
||||
This bit is still extremely janky; it should instead be a magic channel type or
|
||||
action button of some kind. It is, however, functional.
|
||||
|
||||
Delta wants us to enable the "Send copy to self" option in settings. That's
|
||||
exposed as "Bcc self" in the advanced options in Empathy. Once enabled, messages
|
||||
you send via Padfoot will appear in other clients. However, messages you send
|
||||
from other clients don't appear in Padfoot yet because it mostly ignores self
|
||||
messages as a dirty hack to avoid double-showing messages. Progess though.
|
||||
|
||||
### Autogenerated telepathy DBUS bindings
|
||||
|
||||
It makes use of the `dbus-codegen-rust` crate to convert the
|
||||
[telepathy interface specs](https://github.com/TelepathyIM/telepathy-spec) into
|
||||
@@ -62,10 +198,10 @@ regenerated like so:
|
||||
```bash
|
||||
$ git submodule init telepathy-spec
|
||||
$ git submodule update telepathy-spec
|
||||
$ cargo install dbus-codegen-rust
|
||||
$ cargo install dbus-codegen
|
||||
$ ./scripts/dbus-codegen
|
||||
```
|
||||
|
||||
`dbus-codegen-rust` doesn't seem to handle namespaced attributes properly, so
|
||||
we modify the XML files in `telepathy-spec`... with `sed`. The `tp:type`
|
||||
attribute is renamed to `tp:typehint`.
|
||||
attribute is renamed to `tp:typehint`. This will be fixed in the next release.
|
||||
|
70
about.hbs
Normal file
70
about.hbs
Normal file
@@ -0,0 +1,70 @@
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<style>
|
||||
@media (prefers-color-scheme: dark) {
|
||||
body {
|
||||
background: #333;
|
||||
color: white;
|
||||
}
|
||||
a {
|
||||
color: skyblue;
|
||||
}
|
||||
}
|
||||
.container {
|
||||
font-family: sans-serif;
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
.intro {
|
||||
text-align: center;
|
||||
}
|
||||
.licenses-list {
|
||||
list-style-type: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
.license-used-by {
|
||||
margin-top: -10px;
|
||||
}
|
||||
.license-text {
|
||||
max-height: 200px;
|
||||
overflow-y: scroll;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<main class="container">
|
||||
<div class="intro">
|
||||
<h1>Third Party Licenses</h1>
|
||||
<p>This page lists the licenses of the projects used in telepathy-padfoot.</p>
|
||||
</div>
|
||||
|
||||
<h2>Overview of licenses:</h2>
|
||||
<ul class="licenses-overview">
|
||||
{{#each overview}}
|
||||
<li><a href="#{{id}}">{{name}}</a> ({{count}})</li>
|
||||
{{/each}}
|
||||
</ul>
|
||||
|
||||
<h2>All license text:</h2>
|
||||
<ul class="licenses-list">
|
||||
{{#each licenses}}
|
||||
<li class="license">
|
||||
<h3 id="{{id}}">{{name}}</h3>
|
||||
<h4>Used by:</h4>
|
||||
<ul class="license-used-by">
|
||||
{{#each used_by}}
|
||||
<li><a href="{{#if crate.repository}} {{crate.repository}} {{else}} https://crates.io/crates/{{crate.name}} {{/if}}">{{crate.name}} {{crate.version}}</a></li>
|
||||
{{/each}}
|
||||
</ul>
|
||||
<pre class="license-text">{{text}}</pre>
|
||||
</li>
|
||||
{{/each}}
|
||||
</ul>
|
||||
</main>
|
||||
</body>
|
||||
|
||||
</html>
|
11
about.toml
Normal file
11
about.toml
Normal file
@@ -0,0 +1,11 @@
|
||||
accepted = [
|
||||
"0BSD",
|
||||
"Apache-2.0",
|
||||
"BSD-2-Clause",
|
||||
"BSD-3-Clause",
|
||||
"ISC",
|
||||
"CC0-1.0",
|
||||
"MIT",
|
||||
"MPL-2.0",
|
||||
"Zlib"
|
||||
]
|
9555
license.html
Normal file
9555
license.html
Normal file
File diff suppressed because it is too large
Load Diff
1
rust-toolchain
Normal file
1
rust-toolchain
Normal file
@@ -0,0 +1 @@
|
||||
nightly-2020-03-12
|
@@ -12,7 +12,7 @@ rm -f "$dest.rs"
|
||||
rm -rf "$dest"
|
||||
mkdir -p "$dest"
|
||||
|
||||
echo "#![allow(unused)]\n#![allow(clippy::all)]" > "$modfile"
|
||||
echo "#![allow(unused)]" > "$modfile"
|
||||
|
||||
for file in $(ls -a $specs/*.xml); do
|
||||
sed -i 's/tp:type=/tp:typehint=/g' "$file"
|
||||
@@ -31,9 +31,9 @@ for file in $(ls -a $specs/*.xml); do
|
||||
-a AsRefClosure \
|
||||
-o "$out"
|
||||
|
||||
rustfmt $out
|
||||
rustfmt "$out"
|
||||
|
||||
echo "\nmod $name;\npub use self::$name::*;" >> "$modfile"
|
||||
echo "\n#[allow(clippy::all)]\nmod $name;\npub use self::$name::*;" >> "$modfile"
|
||||
done
|
||||
|
||||
git -C telepathy-spec checkout -- .
|
||||
|
@@ -7,16 +7,20 @@ ObjectPath=/org/freedesktop/Telepathy/ConnectionManager/padfoot
|
||||
[Protocol delta]
|
||||
param-account=s required
|
||||
param-password=s required secret
|
||||
param-bcc-self=b
|
||||
status-available=2 settable
|
||||
status-offline = 1 settable
|
||||
|
||||
AuthenticationTypes=org.freedesktop.Telepathy.Channel.Type.ServerTLSConnection;
|
||||
ConnectionInterfaces=org.freedesktop.Telepathy.Connection.Interface.Requests;org.freedesktop.Telepathy.Connection.Interface.Contacts;
|
||||
ConnectionInterfaces=org.freedesktop.Telepathy.Connection.Interface.Avatars;org.freedesktop.Telepathy.Connection.Interface.Contacts;org.freedesktop.Telepathy.Connection.Interface.ContactList;org.freedesktop.Telepathy.Connection.Interface.Requests;org.freedesktop.Telepathy.Connection.Interface.SimplePresence;
|
||||
EnglishName=Delta Chat
|
||||
Icon=im-delta
|
||||
Interfaces=
|
||||
Interfaces=org.freedesktop.Telepathy.Protocol;org.freedesktop.Telepathy.Protocol.Interface.Presence;
|
||||
RequestableChannelClasses=text;
|
||||
VCardField=email
|
||||
|
||||
[text]
|
||||
Interfaces=org.freedesktop.Telepathy.Channel.Interface.Messages;
|
||||
org.freedesktop.Telepathy.Channel.ChannelType s=org.freedesktop.Telepathy.Channel.Type.Text
|
||||
org.freedesktop.Telepathy.Channel.TargetHandleType u=1
|
||||
allowed=org.freedesktop.Telepathy.Channel.TargetHandle;org.freedesktop.Telepathy.Channel.TargetID;
|
||||
allowed=org.freedesktop.Telepathy.Channel.TargetHandle;org.freedesktop.Telepathy.Channel.TargetID;org.freedesktop.Telepathy.Channel.Interface.Messages;
|
||||
|
15
src/main.rs
15
src/main.rs
@@ -13,6 +13,7 @@ use padfoot::{
|
||||
};
|
||||
use std::time::Duration;
|
||||
|
||||
// TODO: move this to the ConnectionManager?
|
||||
fn run() -> Result<()> {
|
||||
let (cm, msg_r) = ConnectionManager::new();
|
||||
let cm_rc = std::rc::Rc::new(cm);
|
||||
@@ -24,7 +25,12 @@ fn run() -> Result<()> {
|
||||
let mut tree = f.tree(());
|
||||
|
||||
let cm_iface = telepathy::connection_manager_server(&f, (), move |_| cm_rc.clone());
|
||||
let proto_iface = telepathy::protocol_server(&f, (), move |_| proto_rc.clone());
|
||||
|
||||
let proto_rc_1 = proto_rc.clone();
|
||||
let proto_iface = telepathy::protocol_server(&f, (), move |_| proto_rc_1.clone());
|
||||
|
||||
let proto_presence_iface =
|
||||
telepathy::protocol_interface_presence_server(&f, (), move |_| proto_rc.clone());
|
||||
|
||||
tree = tree.add(
|
||||
f.object_path(CM_OBJECT_PATH, ())
|
||||
@@ -34,7 +40,8 @@ fn run() -> Result<()> {
|
||||
tree = tree.add(
|
||||
f.object_path(PROTO_OBJECT_PATH, ())
|
||||
.introspectable()
|
||||
.add(proto_iface),
|
||||
.add(proto_iface)
|
||||
.add(proto_presence_iface),
|
||||
);
|
||||
|
||||
tree = tree.add(f.object_path("/", ()).introspectable());
|
||||
@@ -43,8 +50,8 @@ fn run() -> Result<()> {
|
||||
let mut c = LocalConnection::new_session()?;
|
||||
tree.start_receive(&c);
|
||||
|
||||
for name in vec![CM_BUS_NAME, PROTO_BUS_NAME, CM_CONN_BUS_NAME, CONN_BUS_NAME] {
|
||||
let result = c.request_name(name, false, false, true)?;
|
||||
for name in &[CM_BUS_NAME, PROTO_BUS_NAME, CM_CONN_BUS_NAME, CONN_BUS_NAME] {
|
||||
let result = c.request_name(*name, false, false, true)?;
|
||||
match result {
|
||||
RequestNameReply::Exists => {
|
||||
return Err(anyhow!("Another process is already registered on {}", name))
|
||||
|
@@ -1,8 +1,17 @@
|
||||
mod channel;
|
||||
pub use self::channel::*;
|
||||
|
||||
mod connection;
|
||||
pub use self::connection::*;
|
||||
|
||||
mod connection_manager;
|
||||
pub use self::connection_manager::*;
|
||||
|
||||
mod message;
|
||||
pub use self::message::*;
|
||||
|
||||
mod protocol;
|
||||
pub use self::protocol::*;
|
||||
|
||||
mod var_arg;
|
||||
pub use self::var_arg::*;
|
||||
|
197
src/padfoot/channel.rs
Normal file
197
src/padfoot/channel.rs
Normal file
@@ -0,0 +1,197 @@
|
||||
#[allow(clippy::module_inception)]
|
||||
mod channel;
|
||||
pub use channel::*;
|
||||
|
||||
mod messages;
|
||||
pub use messages::*;
|
||||
|
||||
mod type_text;
|
||||
pub use type_text::*;
|
||||
|
||||
use crate::padfoot::{var_bool, var_str, var_str_vec, var_u32, DbusAction, VarArg};
|
||||
use crate::telepathy;
|
||||
|
||||
use async_std::task::block_on;
|
||||
use deltachat as dc;
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::sync::{mpsc, Arc};
|
||||
|
||||
type Result<T> = std::result::Result<T, dbus::tree::MethodErr>;
|
||||
|
||||
pub type HandleType = u32;
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub const HANDLE_TYPE_NONE: HandleType = 0;
|
||||
pub const HANDLE_TYPE_CONTACT: HandleType = 1;
|
||||
#[allow(dead_code)]
|
||||
pub const HANDLE_TYPE_ROOM: HandleType = 2;
|
||||
#[allow(dead_code)]
|
||||
pub const HANDLE_TYPE_LIST: HandleType = 3; // Deprecated
|
||||
#[allow(dead_code)]
|
||||
pub const HANDLE_TYPE_GROUP: HandleType = 4; // Deprecated
|
||||
|
||||
// FIXME: I'm assuming that all channels will be of type text and 1-1 for now.
|
||||
#[derive(Debug)]
|
||||
pub struct Channel {
|
||||
actq: mpsc::Sender<DbusAction>,
|
||||
chat_id: dc::chat::ChatId,
|
||||
ctx: Arc<dc::context::Context>,
|
||||
initiator_handle: u32,
|
||||
path: dbus::Path<'static>,
|
||||
requested: bool,
|
||||
target_handle: u32, // Who we're talking to
|
||||
}
|
||||
|
||||
// "This SHOULD NOT include the channel type and channel interface itself"
|
||||
pub fn channel_interfaces() -> Vec<String> {
|
||||
vec!["org.freedesktop.Telepathy.Channel.Interface.Messages".to_string()]
|
||||
}
|
||||
|
||||
impl Channel {
|
||||
pub fn new(
|
||||
actq: mpsc::Sender<DbusAction>,
|
||||
chat_id: dc::chat::ChatId,
|
||||
ctx: Arc<dc::context::Context>,
|
||||
initiator_handle: u32,
|
||||
path: dbus::Path<'static>,
|
||||
requested: bool,
|
||||
target_handle: u32,
|
||||
) -> Self {
|
||||
Channel {
|
||||
actq,
|
||||
chat_id,
|
||||
ctx,
|
||||
initiator_handle,
|
||||
path,
|
||||
requested,
|
||||
target_handle,
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME: we should be able to introspect this already???
|
||||
pub fn chan_props(&self) -> HashMap<String, VarArg> {
|
||||
let mut out = HashMap::<String, VarArg>::new();
|
||||
out.insert(
|
||||
"org.freedesktop.Telepathy.Channel.ChannelType".to_string(),
|
||||
var_str(self.chan_type()),
|
||||
);
|
||||
out.insert(
|
||||
"org.freedesktop.Telepathy.Channel.TargetHandleType".to_string(),
|
||||
var_u32(self.handle_type()),
|
||||
);
|
||||
out.insert(
|
||||
"org.freedesktop.Telepathy.Channel.InitiatorHandle".to_string(),
|
||||
var_u32(self.initiator_handle),
|
||||
);
|
||||
out.insert(
|
||||
"org.freedesktop.Telepathy.Channel.InitiatorID".to_string(),
|
||||
var_str(self.initiator_contact().unwrap().get_addr().to_string()),
|
||||
);
|
||||
out.insert(
|
||||
"org.freedesktop.Telepathy.Channel.TargetHandle".to_string(),
|
||||
var_u32(self.target_handle),
|
||||
);
|
||||
out.insert(
|
||||
"org.freedesktop.Telepathy.Channel.TargetID".to_string(),
|
||||
var_str(self.target_contact().unwrap().get_addr().to_string()),
|
||||
);
|
||||
out.insert(
|
||||
"org.freedesktop.Telepathy.Channel.Requested".to_string(),
|
||||
var_bool(self.requested),
|
||||
);
|
||||
out.insert(
|
||||
"org.freedesktop.Telepathy.Channel.Interfaces".to_string(),
|
||||
var_str_vec(vec![
|
||||
"org.freedesktop.Telepathy.Channel.Interface.Messages".to_string()
|
||||
]),
|
||||
);
|
||||
|
||||
out
|
||||
}
|
||||
|
||||
pub fn path(&self) -> dbus::Path<'static> {
|
||||
self.path.clone()
|
||||
}
|
||||
|
||||
pub fn chan_type(&self) -> String {
|
||||
"org.freedesktop.Telepathy.Channel.Type.Text".to_string() // FIXME: this shouldn't be hardcoded
|
||||
}
|
||||
|
||||
pub fn handle_type(&self) -> HandleType {
|
||||
HANDLE_TYPE_CONTACT // FIXME: this shouldn't be hardcoded
|
||||
}
|
||||
|
||||
pub fn handle(&self) -> u32 {
|
||||
self.target_handle
|
||||
}
|
||||
|
||||
pub fn target_contact(&self) -> Option<dc::contact::Contact> {
|
||||
block_on(dc::contact::Contact::get_by_id(&self.ctx, self.handle())).ok()
|
||||
}
|
||||
|
||||
pub fn initiator_contact(&self) -> Option<dc::contact::Contact> {
|
||||
block_on(dc::contact::Contact::get_by_id(
|
||||
&self.ctx,
|
||||
self.initiator_handle,
|
||||
))
|
||||
.ok() // FIXME: this will be wrong for outbound channels
|
||||
}
|
||||
|
||||
pub fn requested(&self) -> bool {
|
||||
self.requested
|
||||
}
|
||||
|
||||
pub fn build_object_path(
|
||||
channel: Arc<Channel>,
|
||||
) -> dbus::tree::ObjectPath<dbus::tree::MTFn, ()> {
|
||||
let f = dbus::tree::Factory::new_fn::<()>();
|
||||
|
||||
let c_rc1 = channel.clone();
|
||||
let chan_iface = telepathy::channel_server(&f, (), move |_| c_rc1.clone());
|
||||
|
||||
let c_rc2 = channel.clone();
|
||||
let messages_iface =
|
||||
telepathy::channel_interface_messages_server(&f, (), move |_| c_rc2.clone());
|
||||
|
||||
let c_rc3 = channel.clone();
|
||||
let type_text_iface = telepathy::channel_type_text_server(&f, (), move |_| c_rc3.clone());
|
||||
|
||||
f.object_path(channel.path.clone(), ())
|
||||
.introspectable()
|
||||
.add(chan_iface)
|
||||
.add(messages_iface)
|
||||
.add(type_text_iface)
|
||||
}
|
||||
|
||||
fn try_process_setupmsg(self: &Self, text: String) {
|
||||
if !text.starts_with("IMEX: ") {
|
||||
return;
|
||||
};
|
||||
|
||||
// Expected form: "IMEX: <msg-id> <setupcode>
|
||||
let mut iter = text.split_whitespace();
|
||||
iter.next(); // Ignore the prefix
|
||||
|
||||
let msg_id = match iter.next() {
|
||||
Some(txt) => match txt.parse::<u32>() {
|
||||
Ok(id) => id,
|
||||
_ => return,
|
||||
},
|
||||
_ => return,
|
||||
};
|
||||
|
||||
let setup_code = match iter.next() {
|
||||
Some(txt) => txt,
|
||||
_ => return,
|
||||
};
|
||||
|
||||
if let Err(e) = block_on(dc::imex::continue_key_transfer(
|
||||
&self.ctx,
|
||||
dc::message::MsgId::new(msg_id),
|
||||
&setup_code,
|
||||
)) {
|
||||
println!("Failed to apply setup code {}: {}", msg_id, e);
|
||||
}
|
||||
}
|
||||
}
|
98
src/padfoot/channel/channel.rs
Normal file
98
src/padfoot/channel/channel.rs
Normal file
@@ -0,0 +1,98 @@
|
||||
use crate::padfoot::DbusAction;
|
||||
use crate::telepathy;
|
||||
|
||||
use dbus::tree::MethodErr;
|
||||
|
||||
use super::{Channel, Result};
|
||||
|
||||
impl AsRef<dyn telepathy::Channel + 'static> for std::sync::Arc<Channel> {
|
||||
fn as_ref(&self) -> &(dyn telepathy::Channel + 'static) {
|
||||
&**self
|
||||
}
|
||||
}
|
||||
|
||||
impl telepathy::Channel for Channel {
|
||||
fn close(&self) -> Result<()> {
|
||||
println!("Channel::close()");
|
||||
|
||||
self.actq
|
||||
.send(DbusAction::CloseChannel(self.path()))
|
||||
.unwrap();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Deprecated
|
||||
fn get_channel_type(&self) -> Result<String> {
|
||||
self.channel_type()
|
||||
}
|
||||
|
||||
fn channel_type(&self) -> Result<String> {
|
||||
println!("Channel::channel_type()");
|
||||
|
||||
Ok(self.chan_type())
|
||||
}
|
||||
|
||||
// Deprecated
|
||||
fn get_handle(&self) -> Result<(u32, u32)> {
|
||||
println!("Channel::get_handle()");
|
||||
|
||||
Ok((self.handle_type(), self.handle()))
|
||||
}
|
||||
|
||||
// Deprecated
|
||||
fn get_interfaces(&self) -> Result<Vec<String>> {
|
||||
println!("Channel::get_interfaces()");
|
||||
|
||||
self.interfaces()
|
||||
}
|
||||
|
||||
fn interfaces(&self) -> Result<Vec<String>> {
|
||||
println!("Channel::interfaces()");
|
||||
Ok(super::channel_interfaces())
|
||||
}
|
||||
|
||||
fn target_handle(&self) -> Result<u32> {
|
||||
println!("Channel::target_handle()");
|
||||
|
||||
Ok(self.handle())
|
||||
}
|
||||
|
||||
fn target_id(&self) -> Result<String> {
|
||||
println!("Channel::target_id()");
|
||||
|
||||
if let Some(contact) = self.target_contact() {
|
||||
Ok(contact.get_addr().to_string())
|
||||
} else {
|
||||
Err(MethodErr::no_arg())
|
||||
}
|
||||
}
|
||||
|
||||
fn target_handle_type(&self) -> Result<u32> {
|
||||
println!("Channel::target_handle_type()");
|
||||
|
||||
Ok(self.handle_type())
|
||||
}
|
||||
|
||||
fn requested(&self) -> Result<bool> {
|
||||
println!("Channel::requested()");
|
||||
|
||||
Ok(self.requested) // FIXME: channels initiated by ourselves *will* be requested
|
||||
}
|
||||
|
||||
fn initiator_handle(&self) -> Result<u32> {
|
||||
println!("Channel::initiator_handle()");
|
||||
|
||||
self.target_handle() // FIXME: Not the case for channels initiated by ourselves
|
||||
}
|
||||
|
||||
fn initiator_id(&self) -> Result<String> {
|
||||
println!("Channel::initiator_id()");
|
||||
|
||||
if let Some(contact) = self.initiator_contact() {
|
||||
Ok(contact.get_addr().to_string())
|
||||
} else {
|
||||
Err(MethodErr::no_arg())
|
||||
}
|
||||
}
|
||||
}
|
139
src/padfoot/channel/messages.rs
Normal file
139
src/padfoot/channel/messages.rs
Normal file
@@ -0,0 +1,139 @@
|
||||
use crate::padfoot::{convert_msg, DbusAction, VarArg};
|
||||
use crate::telepathy;
|
||||
|
||||
use async_std::task::block_on;
|
||||
use dbus::message::SignalArgs;
|
||||
use dbus::tree::MethodErr;
|
||||
use dc::constants::Viewtype;
|
||||
use dc::message::{Message, MessageState};
|
||||
use deltachat as dc;
|
||||
use std::collections::HashMap;
|
||||
|
||||
use super::{Channel, Result};
|
||||
|
||||
impl AsRef<dyn telepathy::ChannelInterfaceMessages + 'static> for std::sync::Arc<Channel> {
|
||||
fn as_ref(&self) -> &(dyn telepathy::ChannelInterfaceMessages + 'static) {
|
||||
&**self
|
||||
}
|
||||
}
|
||||
|
||||
impl telepathy::ChannelInterfaceMessages for Channel {
|
||||
fn send_message(&self, parts: Vec<HashMap<&str, VarArg>>, flags: u32) -> Result<String> {
|
||||
println!("Channel::send_message({:?}, {})", parts, flags);
|
||||
|
||||
if parts.len() != 2 {
|
||||
return Err(MethodErr::no_arg());
|
||||
}
|
||||
let _meta = &parts[0];
|
||||
let content = &parts[1];
|
||||
|
||||
let content_type = content["content-type"].0.as_str().unwrap();
|
||||
|
||||
if content_type != "text/plain" {
|
||||
println!("FIXME: can only send text/plain messages right now");
|
||||
return Err(MethodErr::no_arg());
|
||||
}
|
||||
|
||||
let text_opt = content["content"].0.as_str().map(|s| s.to_string());
|
||||
|
||||
let mut delta_msg = Message::new(Viewtype::Text); // FIXME: this won't always be plaintext
|
||||
delta_msg.set_text(text_opt.clone());
|
||||
|
||||
if let Some(text) = text_opt.clone() {
|
||||
self.try_process_setupmsg(text);
|
||||
};
|
||||
|
||||
let ctx = &self.ctx;
|
||||
let blobdir = ctx.get_blobdir();
|
||||
|
||||
let msg_id = match block_on(dc::chat::send_msg(&ctx, self.chat_id, &mut delta_msg)) {
|
||||
Ok(msg_id) => msg_id,
|
||||
Err(e) => {
|
||||
println!(" Failed to send message: {}", e);
|
||||
return Err(MethodErr::no_arg());
|
||||
}
|
||||
};
|
||||
|
||||
let token = format!("{}", msg_id.to_u32());
|
||||
let dbus_parts = convert_msg(blobdir, &delta_msg).map_err(|_| MethodErr::no_arg())?;
|
||||
|
||||
let messages_sig = telepathy::ChannelInterfaceMessagesMessageSent {
|
||||
content: dbus_parts,
|
||||
flags: 0,
|
||||
message_token: token.clone(),
|
||||
}
|
||||
.to_emit_message(&self.path());
|
||||
|
||||
let text_sig = telepathy::ChannelTypeTextSent {
|
||||
timestamp: delta_msg.get_timestamp() as u32,
|
||||
type_: 0,
|
||||
text: text_opt.or_else(|| Some("".to_string())).unwrap(),
|
||||
}
|
||||
.to_emit_message(&self.path());
|
||||
|
||||
self.actq.send(DbusAction::Signal(messages_sig)).unwrap();
|
||||
self.actq.send(DbusAction::Signal(text_sig)).unwrap();
|
||||
|
||||
Ok(token)
|
||||
}
|
||||
|
||||
fn get_pending_message_content(
|
||||
&self,
|
||||
message_id: u32,
|
||||
parts: Vec<u32>,
|
||||
) -> Result<HashMap<u32, VarArg>> {
|
||||
println!(
|
||||
"Channel::get_pending_message_content({}, {:?})",
|
||||
message_id, parts
|
||||
);
|
||||
Err(MethodErr::no_arg())
|
||||
}
|
||||
|
||||
fn supported_content_types(&self) -> Result<Vec<String>> {
|
||||
println!("Channel::supported_content_types()");
|
||||
|
||||
Ok(vec!["*/*".to_string()])
|
||||
}
|
||||
|
||||
fn message_types(&self) -> Result<Vec<u32>> {
|
||||
println!("Channel::message_types()");
|
||||
|
||||
Ok(vec![0]) // Normal messages. FIXME: MDNs too
|
||||
}
|
||||
|
||||
fn message_part_support_flags(&self) -> Result<u32> {
|
||||
println!("Channel::message_part_support_flags()");
|
||||
|
||||
Ok(0) // FIXME: support multipart messages
|
||||
}
|
||||
|
||||
// Return value is an array of array of message parts
|
||||
fn pending_messages(&self) -> Result<Vec<Vec<HashMap<String, VarArg>>>> {
|
||||
println!("Channel::pending_messages()");
|
||||
|
||||
let mut out = Vec::<Vec<HashMap<String, VarArg>>>::new();
|
||||
let ctx = &self.ctx;
|
||||
let blobdir = ctx.get_blobdir();
|
||||
|
||||
for msg_id in block_on(dc::chat::get_chat_msgs(ctx, self.chat_id, 0, None)) {
|
||||
if let Ok(msg) = block_on(dc::message::Message::load_from_db(ctx, msg_id)) {
|
||||
match msg.get_state() {
|
||||
MessageState::InFresh | MessageState::InNoticed => {
|
||||
println!(" A message: {:?}", msg);
|
||||
let parts = convert_msg(blobdir, &msg).map_err(|_| MethodErr::no_arg())?;
|
||||
out.push(parts);
|
||||
}
|
||||
_ => continue,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(out) // FIXME: check for pending messages
|
||||
}
|
||||
|
||||
fn delivery_reporting_support(&self) -> Result<u32> {
|
||||
println!("Channel::delivery_reporting_support()");
|
||||
|
||||
Ok(0) // FIXME: MDNs
|
||||
}
|
||||
}
|
74
src/padfoot/channel/type_text.rs
Normal file
74
src/padfoot/channel/type_text.rs
Normal file
@@ -0,0 +1,74 @@
|
||||
use crate::padfoot::DbusAction;
|
||||
use crate::telepathy;
|
||||
use crate::telepathy::ChannelInterfaceMessages;
|
||||
|
||||
use async_std::task::block_on;
|
||||
use dbus::message::SignalArgs;
|
||||
use dbus::tree::MethodErr;
|
||||
use dc::message::MsgId;
|
||||
use deltachat as dc;
|
||||
|
||||
use super::{Channel, Result};
|
||||
|
||||
impl AsRef<dyn telepathy::ChannelTypeText + 'static> for std::sync::Arc<Channel> {
|
||||
fn as_ref(&self) -> &(dyn telepathy::ChannelTypeText + 'static) {
|
||||
&**self
|
||||
}
|
||||
}
|
||||
|
||||
type PendingMessagesSpec = (
|
||||
u32, // numeric identifier
|
||||
u32, // Unix timestamp indicating when the message was received
|
||||
u32, // contact handle for the contact who sent the message
|
||||
u32, // message type, taken from ChannelTextMessageType
|
||||
u32, // bitwise-OR of the message flags from ChannelTextMessageFlags
|
||||
String, // text of the message
|
||||
);
|
||||
|
||||
// Most of these methods are deprecated, so should be implemented in terms of
|
||||
// the mandatory Messages interface.
|
||||
impl telepathy::ChannelTypeText for Channel {
|
||||
// ids is a list of deltachat msg_ids
|
||||
fn acknowledge_pending_messages(&self, ids: Vec<u32>) -> Result<()> {
|
||||
println!("Channel::acknowledge_pending_messages({:?})", ids);
|
||||
|
||||
let mut msg_ids = Vec::<MsgId>::new();
|
||||
for msg_id in &ids {
|
||||
msg_ids.push(MsgId::new(*msg_id));
|
||||
}
|
||||
|
||||
print!(" Marking messages as seen...");
|
||||
let result = block_on(dc::message::markseen_msgs(&self.ctx, msg_ids));
|
||||
if result {
|
||||
println!("OK!");
|
||||
|
||||
// Emit a PendingMessagesRemoved signal only if all have been removed
|
||||
let sig =
|
||||
telepathy::ChannelInterfaceMessagesPendingMessagesRemoved { message_ids: ids }
|
||||
.to_emit_message(&self.path());
|
||||
|
||||
self.actq.send(DbusAction::Signal(sig)).unwrap();
|
||||
} else {
|
||||
println!("FAILED!");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_message_types(&self) -> Result<Vec<u32>> {
|
||||
println!("Channel::get_message_types()");
|
||||
|
||||
self.message_types()
|
||||
}
|
||||
|
||||
fn list_pending_messages(&self, clear: bool) -> Result<Vec<PendingMessagesSpec>> {
|
||||
println!("Channel::list_pending_messages({})", clear);
|
||||
|
||||
Err(MethodErr::no_arg())
|
||||
}
|
||||
|
||||
fn send(&self, message_type: u32, text: &str) -> Result<()> {
|
||||
println!("Channel::send({}, {})", message_type, text);
|
||||
Err(MethodErr::no_arg())
|
||||
}
|
||||
}
|
@@ -1,84 +1,91 @@
|
||||
mod avatars;
|
||||
pub use self::avatars::*;
|
||||
|
||||
#[allow(clippy::module_inception)]
|
||||
mod connection;
|
||||
pub use self::connection::*;
|
||||
|
||||
mod contacts;
|
||||
pub use self::contacts::*;
|
||||
|
||||
mod contact_list;
|
||||
pub use self::contact_list::*;
|
||||
|
||||
mod escape;
|
||||
use self::escape::escape;
|
||||
|
||||
mod requests;
|
||||
pub use self::requests::*;
|
||||
|
||||
mod simple_presence;
|
||||
pub use self::simple_presence::*;
|
||||
|
||||
use crate::padfoot::{convert_msg, Channel, VarArg};
|
||||
use crate::telepathy;
|
||||
|
||||
use async_std::task::block_on;
|
||||
use dbus::blocking::{stdintf::org_freedesktop_dbus::RequestNameReply, LocalConnection};
|
||||
use dbus::tree;
|
||||
use dbus::channel::{MatchingReceiver, Sender};
|
||||
use dbus::message::SignalArgs;
|
||||
use dbus::tree::MethodErr;
|
||||
|
||||
use dc::config::Config;
|
||||
use dc::context::Context;
|
||||
use dc::Event;
|
||||
use deltachat as dc;
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::sync::{Arc, RwLock};
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::sync::{mpsc, Arc, Mutex, RwLock};
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
|
||||
pub const CONN_BUS_NAME: &'static str = "org.freedesktop.Telepathy.Connection.padfoot.delta";
|
||||
pub const CONN_OBJECT_PATH: &'static str = "/org/freedesktop/Telepathy/Connection/padfoot/delta";
|
||||
pub const CONN_BUS_NAME: &str = "org.freedesktop.Telepathy.Connection.padfoot.delta";
|
||||
pub const CONN_OBJECT_PATH: &str = "/org/freedesktop/Telepathy/Connection/padfoot/delta";
|
||||
|
||||
// Only the main loop has access to the DBUS connection. Interacting with DBUS
|
||||
// outside of method return values requires one of these to be added to actq
|
||||
#[derive(Debug)]
|
||||
pub enum DbusAction {
|
||||
Signal(dbus::Message), // Generic signal to send
|
||||
|
||||
NewChannel(Channel), // Add this channel
|
||||
CloseChannel(dbus::Path<'static>), // Close this channel
|
||||
|
||||
IncomingMessage(dc::chat::ChatId, dc::message::MsgId), // Look at this \o/
|
||||
FreshMessages, // Hint that some messages need looking at
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
// A connection uses delta database IDs as handles, and email addresses as IDs
|
||||
pub struct Connection {
|
||||
// Used for sending out messages
|
||||
actq: mpsc::Sender<DbusAction>,
|
||||
// actq: Arc<Mutex<VecDeque<DbusAction>>>,
|
||||
|
||||
// Channels we own
|
||||
channels: Arc<RwLock<HashMap<dbus::Path<'static>, Arc<Channel>>>>,
|
||||
|
||||
// Owned by the CM. Remove ourselves from this when done
|
||||
conns: Arc<Mutex<HashSet<dbus::Path<'static>>>>,
|
||||
|
||||
ctx: Arc<Context>, // Delta contexts are threadsafe
|
||||
settings: ConnSettings,
|
||||
state: Arc<RwLock<ConnState>>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ConnSettings {
|
||||
account: String,
|
||||
password: String,
|
||||
bcc_self: bool,
|
||||
id: String,
|
||||
ctx: Arc<RwLock<Context>>,
|
||||
}
|
||||
|
||||
fn escape_one(b: u8) -> String {
|
||||
format!("_{:0<2x}", b)
|
||||
}
|
||||
impl ConnSettings {
|
||||
pub fn from_params(params: HashMap<&str, VarArg>) -> Result<Self, MethodErr> {
|
||||
let err = Err(MethodErr::no_arg());
|
||||
|
||||
// Some non-empty sequence of ASCII letters, digits and underscores
|
||||
fn escape(s: String) -> String {
|
||||
// Special-case the empty string
|
||||
if s.len() == 0 {
|
||||
return "_".to_string();
|
||||
}
|
||||
|
||||
let bytes = s.into_bytes();
|
||||
let mut iter = bytes.iter();
|
||||
let mut out = String::new();
|
||||
|
||||
// Only alphanumeric in the first byte
|
||||
let x = *iter.next().expect("Already checked len > 0");
|
||||
let first = match x {
|
||||
b'a'..=b'z' | b'A'..=b'Z' => unsafe { String::from_utf8_unchecked(vec![x]) },
|
||||
_ => escape_one(x),
|
||||
};
|
||||
|
||||
out.push_str(&first);
|
||||
|
||||
for x in iter {
|
||||
let next = match x {
|
||||
b'a'..=b'z' | b'A'..=b'Z' | b'0'..=b'9' => unsafe {
|
||||
String::from_utf8_unchecked(vec![*x])
|
||||
},
|
||||
_ => escape_one(*x),
|
||||
};
|
||||
|
||||
out.push_str(&next);
|
||||
}
|
||||
|
||||
out
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_escape() {
|
||||
assert_eq!(escape("".to_string()), "_");
|
||||
assert_eq!(escape("foo".to_string()), "foo");
|
||||
assert_eq!(escape("foo@bar".to_string()), "foo_40bar");
|
||||
assert_eq!(escape("foo_bar".to_string()), "foo_5fbar");
|
||||
assert_eq!(escape("foo__@__bar".to_string()), "foo_5f_5f_40_5f_5fbar");
|
||||
assert_eq!(escape("1foo".to_string()), "_31foo");
|
||||
}
|
||||
}
|
||||
|
||||
impl Connection {
|
||||
pub fn new(params: HashMap<&str, super::Variant>) -> Result<Self, dbus::tree::MethodErr> {
|
||||
let err = Err(dbus::tree::MethodErr::no_arg());
|
||||
|
||||
let acct = match params.get("account") {
|
||||
let account = match params.get("account") {
|
||||
Some(variant) => match variant.0.as_str() {
|
||||
Some(str) => str.to_string(),
|
||||
None => return err,
|
||||
@@ -86,7 +93,7 @@ impl Connection {
|
||||
None => return err,
|
||||
};
|
||||
|
||||
let id = escape(acct.to_owned());
|
||||
let id = escape(account.to_owned());
|
||||
|
||||
let password = match params.get("password") {
|
||||
Some(variant) => match variant.0.as_str() {
|
||||
@@ -96,66 +103,182 @@ impl Connection {
|
||||
None => return err,
|
||||
};
|
||||
|
||||
let mut dbfile = directories::ProjectDirs::from("gs", "ur", "telepathy-padfoot")
|
||||
.ok_or_else(|| tree::MethodErr::no_arg())
|
||||
.and_then(|p| Ok(p.data_local_dir().to_path_buf()))?;
|
||||
|
||||
dbfile.push(&id);
|
||||
dbfile.push("db.sqlite3");
|
||||
|
||||
// FIXME: how to give it access to the connection (initialized later)?
|
||||
let id2 = id.clone();
|
||||
let f = move |_c: &Context, e: Event| {
|
||||
match e {
|
||||
Event::Info(msg) => println!("Connection<{}>: INFO: {}", id2, msg),
|
||||
Event::Warning(msg) => println!("Connection<{}>: WARN : {}", id2, msg),
|
||||
Event::Error(msg) | Event::ErrorNetwork(msg) | Event::ErrorSelfNotInGroup(msg) => {
|
||||
println!("Connection<{}>: ERR : {}", id2, msg)
|
||||
let bcc_self = match params.get("bcc-self") {
|
||||
Some(variant) => match variant.0.as_u64() {
|
||||
Some(i) => i != 0,
|
||||
None => {
|
||||
println!("0!");
|
||||
return err;
|
||||
}
|
||||
_ => println!("Connection<{}>: unhandled event received: {:?}", id2, e),
|
||||
};
|
||||
},
|
||||
None => {
|
||||
println!("1!");
|
||||
false
|
||||
}
|
||||
};
|
||||
|
||||
let ctx =
|
||||
Context::new(Box::new(f), "telepathy-padfoot".to_string(), dbfile).map_err(|e| {
|
||||
println!("Connection<{}>: couldn't get delta context: {}", id, e);
|
||||
tree::MethodErr::no_arg() // FIXME: better error handling
|
||||
})?;
|
||||
|
||||
ctx.set_config(Config::Addr, Some(&acct))
|
||||
.map_err(|_e| tree::MethodErr::no_arg())?;
|
||||
ctx.set_config(Config::MailPw, Some(&password))
|
||||
.map_err(|_e| tree::MethodErr::no_arg())?;
|
||||
ctx.set_config(Config::SentboxWatch, Some(&"Sent"))
|
||||
.map_err(|_e| tree::MethodErr::no_arg())?;
|
||||
|
||||
if !ctx.is_configured() {
|
||||
ctx.configure();
|
||||
};
|
||||
|
||||
Ok(Connection {
|
||||
id: id,
|
||||
ctx: Arc::new(RwLock::new(ctx)),
|
||||
Ok(Self {
|
||||
account,
|
||||
password,
|
||||
bcc_self,
|
||||
id,
|
||||
})
|
||||
}
|
||||
|
||||
// This is run inside its own thread
|
||||
pub fn id(&self) -> String {
|
||||
self.id.to_owned()
|
||||
}
|
||||
|
||||
pub fn bus(&self) -> String {
|
||||
CONN_BUS_NAME.to_owned() + "." + &self.id
|
||||
}
|
||||
|
||||
pub fn path(&self) -> dbus::Path<'static> {
|
||||
dbus::Path::new(format!("{}/{}", CONN_OBJECT_PATH, &self.id)).expect("Valid path")
|
||||
}
|
||||
}
|
||||
|
||||
impl Connection {
|
||||
pub fn new(
|
||||
settings: ConnSettings,
|
||||
conns: Arc<Mutex<HashSet<dbus::Path<'static>>>>,
|
||||
) -> Result<(Self, mpsc::Receiver<DbusAction>), MethodErr> {
|
||||
let proj_dir = directories::ProjectDirs::from("gs", "ur", "telepathy-padfoot")
|
||||
.ok_or_else(MethodErr::no_arg)?;
|
||||
|
||||
let mut dbfile = async_std::path::PathBuf::new();
|
||||
dbfile.push(proj_dir.data_local_dir().to_str().unwrap());
|
||||
|
||||
dbfile.push(settings.id());
|
||||
dbfile.push("db.sqlite3");
|
||||
|
||||
let (q_s, q_r) = mpsc::channel::<DbusAction>();
|
||||
let id = settings.id();
|
||||
|
||||
let ctx = Arc::new(
|
||||
block_on(Context::new("telepathy-padfoot".to_string(), dbfile)).map_err(|e| {
|
||||
println!(
|
||||
"Connection<{}>::new(): couldn't get delta context: {}",
|
||||
settings.id(),
|
||||
e
|
||||
);
|
||||
MethodErr::no_arg() // FIXME: better error handling
|
||||
})?,
|
||||
);
|
||||
|
||||
let e_ctx = ctx.clone();
|
||||
let e_queue = q_s.clone();
|
||||
thread::spawn(move || {
|
||||
let emitter = e_ctx.get_event_emitter();
|
||||
while let Some(e) = emitter.recv_sync() {
|
||||
match e {
|
||||
Event::Info(msg) => println!("Connection<{}>: INFO: {}", id, msg),
|
||||
Event::Warning(msg) => println!("Connection<{}>: WARN : {}", id, msg),
|
||||
Event::Error(msg)
|
||||
| Event::ErrorNetwork(msg)
|
||||
| Event::ErrorSelfNotInGroup(msg) => {
|
||||
println!("Connection<{}>: ERR : {}", id, msg)
|
||||
}
|
||||
Event::ConfigureProgress(progress) => {
|
||||
println!("Connection<{}>: Configuration progress: {}", id, progress)
|
||||
}
|
||||
Event::ImapConnected(msg) | Event::SmtpConnected(msg) => {
|
||||
println!("Connection<{}>: Network: {}", id, msg);
|
||||
}
|
||||
Event::MsgsChanged { chat_id, msg_id } => {
|
||||
println!(
|
||||
"Connection<{}>: Messages changed for {}: {}",
|
||||
id, chat_id, msg_id
|
||||
);
|
||||
e_queue
|
||||
.send(DbusAction::IncomingMessage(chat_id, msg_id))
|
||||
.unwrap();
|
||||
}
|
||||
Event::IncomingMsg { chat_id, msg_id } => {
|
||||
println!(
|
||||
"Connection<{}>: Incoming message for {}: {}",
|
||||
id, chat_id, msg_id
|
||||
);
|
||||
e_queue
|
||||
.send(DbusAction::IncomingMessage(chat_id, msg_id))
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
/* Unhandled messages:
|
||||
SmtpMessageSent(String),
|
||||
ImapMessageDeleted(String),
|
||||
ImapFolderEmptied(String),
|
||||
NewBlobFile(String),
|
||||
DeletedBlobFile(String),
|
||||
MsgDelivered
|
||||
MsgFailed
|
||||
MsgRead
|
||||
ChatModified(ChatId),
|
||||
ContactsChanged(Option<u32>),
|
||||
LocationChanged(Option<u32>),
|
||||
ImexProgress(usize),
|
||||
ImexFileWritten(PathBuf),
|
||||
SecurejoinInviterProgress
|
||||
SecurejoinJoinerProgress
|
||||
*/
|
||||
Event::ImapMessageMoved(_) | Event::ImapMessageDeleted(_) => {}
|
||||
_ => println!("Connection<{}>: unhandled event received: {:?}", id, e),
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
block_on(ctx.set_config(Config::Addr, Some(&settings.account)))
|
||||
.map_err(|_e| MethodErr::no_arg())?;
|
||||
block_on(ctx.set_config(Config::MailPw, Some(&settings.password)))
|
||||
.map_err(|_e| MethodErr::no_arg())?;
|
||||
|
||||
if settings.bcc_self {
|
||||
block_on(ctx.set_config(Config::BccSelf, Some(&"1")))
|
||||
.map_err(|_e| MethodErr::no_arg())?;
|
||||
} else {
|
||||
block_on(ctx.set_config(Config::BccSelf, Some(&"0")))
|
||||
.map_err(|_e| MethodErr::no_arg())?;
|
||||
}
|
||||
|
||||
if !block_on(ctx.is_configured()) {
|
||||
block_on(ctx.configure()).unwrap();
|
||||
};
|
||||
|
||||
Ok((
|
||||
Connection {
|
||||
actq: q_s,
|
||||
channels: Arc::new(RwLock::new(
|
||||
HashMap::<dbus::Path<'static>, Arc<Channel>>::new(),
|
||||
)),
|
||||
conns,
|
||||
ctx,
|
||||
settings,
|
||||
state: Arc::new(RwLock::new(ConnState::Initial)),
|
||||
},
|
||||
q_r,
|
||||
))
|
||||
}
|
||||
|
||||
// This should be run inside its own thread. It will signal via the channel
|
||||
// once the main loop is ready
|
||||
//
|
||||
// FIXME: running several +process+ loops sure is convenient, but it also
|
||||
// seems inefficient...
|
||||
pub fn run(self) {
|
||||
pub fn run(
|
||||
self,
|
||||
done_signal: mpsc::Sender<Option<MethodErr>>,
|
||||
queue_receiver: mpsc::Receiver<DbusAction>,
|
||||
) {
|
||||
let id = self.id();
|
||||
let bus = self.bus();
|
||||
let path = self.path();
|
||||
|
||||
let conns = self.conns.clone();
|
||||
let chans = self.channels.clone();
|
||||
let actq = self.actq.clone();
|
||||
let ctx = self.ctx.clone();
|
||||
let c_rc = std::rc::Rc::new(self);
|
||||
|
||||
let f = tree::Factory::new_fn::<()>();
|
||||
let iface = telepathy::connection_server(&f, (), move |_| c_rc.clone());
|
||||
|
||||
let mut tree = f.tree(());
|
||||
tree = tree.add(f.object_path(path, ()).introspectable().add(iface));
|
||||
|
||||
tree = tree.add(f.object_path("/", ()).introspectable());
|
||||
let state = self.state.clone();
|
||||
let tree = self.build_tree();
|
||||
|
||||
// Setup DBus connection
|
||||
let mut c = match LocalConnection::new_session() {
|
||||
@@ -166,227 +289,282 @@ impl Connection {
|
||||
}
|
||||
};
|
||||
|
||||
tree.start_receive(&c);
|
||||
let tc = tree.clone();
|
||||
c.start_receive(
|
||||
dbus::message::MatchRule::new_method_call(),
|
||||
Box::new(move |msg, c| {
|
||||
let tree = tc.lock().unwrap();
|
||||
if let Some(replies) = tree.handle(&msg) {
|
||||
for r in replies {
|
||||
let _ = c.send(r);
|
||||
}
|
||||
}
|
||||
true
|
||||
}),
|
||||
);
|
||||
|
||||
match c.request_name(bus.clone(), false, false, true) {
|
||||
Ok(RequestNameReply::Exists) => {
|
||||
println!("Another process is already registered on {}", bus);
|
||||
done_signal.send(Some(MethodErr::no_arg())).unwrap();
|
||||
return;
|
||||
}
|
||||
Err(e) => {
|
||||
println!("Failed to register {}: {}", bus, e);
|
||||
done_signal.send(Some(MethodErr::no_arg())).unwrap();
|
||||
return;
|
||||
}
|
||||
_ => println!("{} listening on {}", c.unique_name(), bus), // All other responses we can get are a success
|
||||
_ => {
|
||||
// All other responses we can get are a success. We are now on
|
||||
// the message bus, so the caller can proceed
|
||||
println!("{} listening on {}", c.unique_name(), bus);
|
||||
done_signal.send(None).unwrap();
|
||||
}
|
||||
};
|
||||
|
||||
// Set up delta jobs last in case registering to DBUS fails
|
||||
// "Borrowed" from https://github.com/deltachat/deltachat-core-rust/blob/master/examples/simple.rs
|
||||
let running = Arc::new(RwLock::new(true));
|
||||
while *state.read().unwrap() != ConnState::Disconnected {
|
||||
if let Err(e) = c.process(Duration::from_millis(100)) {
|
||||
println!("Error processing: {}", e);
|
||||
|
||||
let inbox_ctx = ctx.clone();
|
||||
let r1 = running.clone();
|
||||
let _t1 = thread::spawn(move || {
|
||||
while *r1.read().unwrap() {
|
||||
dc::job::perform_inbox_jobs(&inbox_ctx.read().unwrap());
|
||||
if *r1.read().unwrap() {
|
||||
dc::job::perform_inbox_fetch(&inbox_ctx.read().unwrap());
|
||||
break;
|
||||
};
|
||||
|
||||
if *r1.read().unwrap() {
|
||||
dc::job::perform_inbox_idle(&inbox_ctx.read().unwrap());
|
||||
// Spend a bit of time sending any outgoing messages - signals, mostly
|
||||
while let Some(act) = queue_receiver.try_recv().ok() {
|
||||
match act {
|
||||
DbusAction::Signal(msg) => {
|
||||
print!("*** Connection<{}>: Sending signal: {:?}...", id, msg);
|
||||
|
||||
match c.send(msg) {
|
||||
Err(e) => println!("error! {:?}", e), // FIXME: handle error better?
|
||||
_ => println!("OK!"),
|
||||
}
|
||||
}
|
||||
DbusAction::NewChannel(channel) => {
|
||||
let chan_type = channel.chan_type();
|
||||
let handle_type = channel.handle_type();
|
||||
let handle = channel.handle();
|
||||
let chan_path = channel.path().clone();
|
||||
let chan_props = channel.chan_props();
|
||||
let rc_channel = Arc::new(channel);
|
||||
|
||||
println!("*** Creating channel {}", chan_path);
|
||||
|
||||
Arc::clone(&chans)
|
||||
.write()
|
||||
.unwrap()
|
||||
.insert(chan_path.clone(), rc_channel.clone());
|
||||
|
||||
let t2 = tree.clone();
|
||||
let op = Channel::build_object_path(rc_channel);
|
||||
|
||||
t2.lock().unwrap().insert(op);
|
||||
|
||||
let requests_sig = telepathy::ConnectionInterfaceRequestsNewChannels {
|
||||
channels: vec![(chan_path.clone(), chan_props)],
|
||||
};
|
||||
|
||||
let legacy_sig = telepathy::ConnectionNewChannel {
|
||||
object_path: chan_path.clone(),
|
||||
channel_type: chan_type,
|
||||
handle_type, // contact. FIXME: support other channel types
|
||||
handle, // id of other contact
|
||||
// TODO: initiator needs to be tracked
|
||||
suppress_handler: false, // We'll need to start passing this
|
||||
};
|
||||
|
||||
actq.send(DbusAction::Signal(requests_sig.to_emit_message(&path)))
|
||||
.unwrap();
|
||||
actq.send(DbusAction::Signal(legacy_sig.to_emit_message(&path)))
|
||||
.unwrap();
|
||||
}
|
||||
DbusAction::CloseChannel(chan_path) => {
|
||||
println!("*** Closing channel {}", chan_path.clone());
|
||||
let _chan = Arc::clone(&chans).write().unwrap().remove(&chan_path);
|
||||
let t2 = tree.clone();
|
||||
t2.lock().unwrap().remove(&chan_path);
|
||||
|
||||
let requests_sig = telepathy::ConnectionInterfaceRequestsChannelClosed {
|
||||
removed: chan_path.clone(),
|
||||
};
|
||||
|
||||
let legacy_sig = telepathy::ChannelClosed {};
|
||||
|
||||
actq.send(DbusAction::Signal(requests_sig.to_emit_message(&path)))
|
||||
.unwrap();
|
||||
actq.send(DbusAction::Signal(legacy_sig.to_emit_message(&chan_path)))
|
||||
.unwrap();
|
||||
}
|
||||
DbusAction::IncomingMessage(chat_id, msg_id) => {
|
||||
println!("*** Incoming message: {} {}", chat_id, msg_id);
|
||||
// TODO: check if we have a channel for the chat
|
||||
let chan_path = Connection::build_channel_path(path.clone(), chat_id);
|
||||
let c2 = Arc::clone(&chans);
|
||||
let chans = c2.read().unwrap();
|
||||
//let u_ctx = ctx.clone();
|
||||
let ctx = ctx.clone();
|
||||
let blobdir = ctx.get_blobdir();
|
||||
|
||||
// Autocreate channel if it doesn't already exist
|
||||
// FIXME: unknown contacts need more care than this
|
||||
if !chans.contains_key(&chan_path) {
|
||||
print!("Channel for {} doesn't exist yet, creating it...", chat_id);
|
||||
|
||||
let contacts = block_on(dc::chat::get_chat_contacts(&ctx, chat_id));
|
||||
if contacts.len() > 1 {
|
||||
println!("...{} contacts in chat, ignoring!", contacts.len());
|
||||
continue;
|
||||
}
|
||||
|
||||
// FIXME: device-specific chat isn't really a self-chat
|
||||
let handle = contacts
|
||||
.first()
|
||||
.unwrap_or(&dc::constants::DC_CONTACT_ID_SELF);
|
||||
let chan = Channel::new(
|
||||
actq.clone(),
|
||||
chat_id,
|
||||
ctx.clone(),
|
||||
*handle, // initiator is the remote contact
|
||||
chan_path,
|
||||
false, // FIXME: this needs to handle requested channels
|
||||
*handle, // target is always the other party
|
||||
);
|
||||
actq.send(DbusAction::NewChannel(chan)).unwrap();
|
||||
actq.send(act).unwrap();
|
||||
|
||||
println!("OK");
|
||||
continue;
|
||||
}
|
||||
|
||||
// Since the channel exists, emit new message signals
|
||||
print!("Message {} received for {}...", msg_id, chan_path);
|
||||
|
||||
let msg = match block_on(dc::message::Message::load_from_db(&ctx, msg_id)) {
|
||||
Ok(m) => m,
|
||||
Err(e) => {
|
||||
println!("Can't load from database, skipping: {}", e);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
// Ignore messages that are self-originated.
|
||||
// FIXME: special-case self-chats
|
||||
if msg.get_from_id() == dc::constants::DC_CONTACT_ID_SELF
|
||||
&& !msg.is_setupmessage()
|
||||
{
|
||||
println!("from ourselves, skipping");
|
||||
continue;
|
||||
}
|
||||
|
||||
let parts = convert_msg(blobdir, &msg);
|
||||
if parts.is_err() {
|
||||
println!("can't convert, skipping: {}", parts.unwrap_err());
|
||||
continue;
|
||||
}
|
||||
|
||||
let sig = telepathy::ChannelInterfaceMessagesMessageReceived {
|
||||
message: parts.unwrap(),
|
||||
}
|
||||
.to_emit_message(&chan_path);
|
||||
|
||||
actq.send(DbusAction::Signal(sig)).unwrap();
|
||||
|
||||
// FIXME: We MUST also send a Text.Received signal
|
||||
|
||||
println!("OK!");
|
||||
}
|
||||
|
||||
DbusAction::FreshMessages => {
|
||||
println!("*** FRESH MESSAGES");
|
||||
let ctx = ctx.clone();
|
||||
|
||||
for msg_id in block_on(dc::context::Context::get_fresh_msgs(&ctx)) {
|
||||
println!(" FRESH MESSAGE: {}", msg_id);
|
||||
match block_on(dc::message::Message::load_from_db(&ctx, msg_id)) {
|
||||
Ok(msg) => {
|
||||
actq.send(DbusAction::IncomingMessage(
|
||||
msg.get_chat_id(),
|
||||
msg_id,
|
||||
))
|
||||
.unwrap();
|
||||
}
|
||||
Err(e) => println!("Couldn't load fresh message {}: {}", msg_id, e),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let smtp_ctx = ctx.clone();
|
||||
let r1 = running.clone();
|
||||
let _t2 = thread::spawn(move || {
|
||||
while *r1.read().unwrap() {
|
||||
dc::job::perform_smtp_jobs(&smtp_ctx.read().unwrap());
|
||||
if *r1.read().unwrap() {
|
||||
dc::job::perform_smtp_idle(&smtp_ctx.read().unwrap());
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let mvbox_ctx = ctx.clone();
|
||||
let r1 = running.clone();
|
||||
let _t3 = thread::spawn(move || {
|
||||
while *r1.read().unwrap() {
|
||||
dc::job::perform_mvbox_fetch(&mvbox_ctx.read().unwrap());
|
||||
if *r1.read().unwrap() {
|
||||
dc::job::perform_mvbox_idle(&mvbox_ctx.read().unwrap());
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let sentbox_ctx = ctx.clone();
|
||||
let r1 = running.clone();
|
||||
let _t4 = thread::spawn(move || {
|
||||
while *r1.read().unwrap() {
|
||||
dc::job::perform_sentbox_fetch(&sentbox_ctx.read().unwrap());
|
||||
if *r1.read().unwrap() {
|
||||
dc::job::perform_sentbox_idle(&sentbox_ctx.read().unwrap());
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let r1 = running.clone();
|
||||
while *r1.read().unwrap() {
|
||||
match c.process(Duration::from_millis(100)) {
|
||||
Err(e) => {
|
||||
println!("Error processing: {}", e);
|
||||
return;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
// TODO: notice when the conn wants to exit
|
||||
}
|
||||
|
||||
// TODO: join on threads started in connect!
|
||||
|
||||
let mut conns = conns.lock().expect("Mutex access");
|
||||
conns.remove(&path);
|
||||
}
|
||||
|
||||
pub fn id(&self) -> String {
|
||||
self.settings.id.to_string()
|
||||
}
|
||||
|
||||
pub fn bus(&self) -> String {
|
||||
CONN_BUS_NAME.to_owned() + "." + &self.id
|
||||
self.settings.bus()
|
||||
}
|
||||
|
||||
pub fn path(&self) -> String {
|
||||
CONN_OBJECT_PATH.to_owned() + "/" + &self.id
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<dyn telepathy::Connection + 'static> for std::rc::Rc<Connection> {
|
||||
fn as_ref(&self) -> &(dyn telepathy::Connection + 'static) {
|
||||
&**self
|
||||
}
|
||||
}
|
||||
|
||||
impl telepathy::Connection for Connection {
|
||||
fn connect(&self) -> Result<(), tree::MethodErr> {
|
||||
Err(tree::MethodErr::no_arg()) // FIXME: should be NotImplemented?
|
||||
}
|
||||
|
||||
fn disconnect(&self) -> Result<(), tree::MethodErr> {
|
||||
println!("Connection<{}>::disconnect()", self.id);
|
||||
Err(tree::MethodErr::no_arg()) // FIXME: should be NotImplemented?
|
||||
}
|
||||
|
||||
fn get_interfaces(&self) -> Result<Vec<String>, tree::MethodErr> {
|
||||
println!("Connection<{}>::get_interfaces()", self.id);
|
||||
Err(tree::MethodErr::no_arg()) // FIXME: should be NotImplemented?
|
||||
}
|
||||
|
||||
fn get_protocol(&self) -> Result<String, tree::MethodErr> {
|
||||
println!("Connection<{}>::get_protocol()", self.id);
|
||||
Ok(super::PROTO_NAME.to_string())
|
||||
}
|
||||
|
||||
fn get_self_handle(&self) -> Result<u32, tree::MethodErr> {
|
||||
println!("Connection<{}>::get_self_handle()", self.id);
|
||||
Err(tree::MethodErr::no_arg()) // FIXME: should be NotImplemented?
|
||||
}
|
||||
|
||||
fn get_status(&self) -> Result<u32, tree::MethodErr> {
|
||||
println!("Connection<{}>::get_status()", self.id);
|
||||
Err(tree::MethodErr::no_arg()) // FIXME: should be NotImplemented?
|
||||
}
|
||||
|
||||
fn hold_handles(&self, handle_type: u32, handles: Vec<u32>) -> Result<(), tree::MethodErr> {
|
||||
println!(
|
||||
"Connection<{}>::hold_handles({}, {:?})",
|
||||
self.id, handle_type, handles
|
||||
);
|
||||
Err(tree::MethodErr::no_arg()) // FIXME: should be NotImplemented?
|
||||
}
|
||||
|
||||
fn inspect_handles(
|
||||
&self,
|
||||
handle_type: u32,
|
||||
handles: Vec<u32>,
|
||||
) -> Result<Vec<String>, tree::MethodErr> {
|
||||
println!(
|
||||
"Connection<{}>::inspect_handles({}, {:?})",
|
||||
self.id, handle_type, handles
|
||||
);
|
||||
Err(tree::MethodErr::no_arg()) // FIXME: should be NotImplemented?
|
||||
}
|
||||
|
||||
fn list_channels(
|
||||
&self,
|
||||
) -> Result<Vec<(dbus::Path<'static>, String, u32, u32)>, tree::MethodErr> {
|
||||
println!("Connection<{}>::list_channels()", self.id);
|
||||
Err(tree::MethodErr::no_arg()) // FIXME: should be NotImplemented?
|
||||
}
|
||||
|
||||
fn release_handles(&self, handle_type: u32, handles: Vec<u32>) -> Result<(), tree::MethodErr> {
|
||||
println!(
|
||||
"Connection<{}>::release_handles({}, {:?})",
|
||||
self.id, handle_type, handles
|
||||
);
|
||||
Err(tree::MethodErr::no_arg()) // FIXME: should be NotImplemented?
|
||||
}
|
||||
|
||||
fn request_channel(
|
||||
&self,
|
||||
type_: &str,
|
||||
handle_type: u32,
|
||||
handle: u32,
|
||||
suppress_handler: bool,
|
||||
) -> Result<dbus::Path<'static>, tree::MethodErr> {
|
||||
println!(
|
||||
"Connection<{}>::request_channel({}, {}, {}, {})",
|
||||
self.id, type_, handle_type, handle, suppress_handler
|
||||
);
|
||||
Err(tree::MethodErr::no_arg()) // FIXME: should be NotImplemented?
|
||||
}
|
||||
|
||||
fn request_handles(
|
||||
&self,
|
||||
handle_type: u32,
|
||||
identifiers: Vec<&str>,
|
||||
) -> Result<Vec<u32>, tree::MethodErr> {
|
||||
println!(
|
||||
"Connection<{}>::request_handles({}, {:?})",
|
||||
self.id, handle_type, identifiers
|
||||
);
|
||||
Err(tree::MethodErr::no_arg()) // FIXME: should be NotImplemented?
|
||||
}
|
||||
|
||||
fn add_client_interest(&self, tokens: Vec<&str>) -> Result<(), tree::MethodErr> {
|
||||
println!("Connection<{}>::add_client_interest({:?})", self.id, tokens);
|
||||
Err(tree::MethodErr::no_arg()) // FIXME: should be NotImplemented?
|
||||
}
|
||||
|
||||
fn remove_client_interest(&self, tokens: Vec<&str>) -> Result<(), tree::MethodErr> {
|
||||
println!(
|
||||
"Connection<{}>::remove_client_interest({:?})",
|
||||
self.id, tokens
|
||||
);
|
||||
Err(tree::MethodErr::no_arg()) // FIXME: should be NotImplemented?
|
||||
}
|
||||
|
||||
fn interfaces(&self) -> Result<Vec<String>, tree::MethodErr> {
|
||||
println!("Connection<{}>::interfaces()", self.id);
|
||||
Err(tree::MethodErr::no_arg()) // FIXME: should be NotImplemented?
|
||||
}
|
||||
|
||||
fn self_handle(&self) -> Result<u32, tree::MethodErr> {
|
||||
println!("Connection<{}>::self_handle()", self.id);
|
||||
Err(tree::MethodErr::no_arg()) // FIXME: should be NotImplemented?
|
||||
}
|
||||
|
||||
fn self_id(&self) -> Result<String, tree::MethodErr> {
|
||||
println!("Connection<{}>::self_id()", self.id);
|
||||
Err(tree::MethodErr::no_arg()) // FIXME: should be NotImplemented?
|
||||
}
|
||||
|
||||
fn status(&self) -> Result<u32, tree::MethodErr> {
|
||||
println!("Connection<{}>::status()", self.id);
|
||||
Err(tree::MethodErr::no_arg()) // FIXME: should be NotImplemented?
|
||||
}
|
||||
|
||||
fn has_immortal_handles(&self) -> Result<bool, tree::MethodErr> {
|
||||
println!("Connection<{}>::has_immortal_handles()", self.id);
|
||||
Ok(true)
|
||||
pub fn path(&self) -> dbus::Path<'static> {
|
||||
self.settings.path()
|
||||
}
|
||||
|
||||
fn build_channel_path(
|
||||
path: dbus::Path<'static>,
|
||||
chat_id: dc::chat::ChatId,
|
||||
) -> dbus::strings::Path<'static> {
|
||||
let path = format!("{}/{}", path, chat_id.to_u32());
|
||||
|
||||
dbus::strings::Path::new(path).expect("Must be valid")
|
||||
}
|
||||
|
||||
fn build_tree(self) -> Arc<Mutex<dbus::tree::Tree<dbus::tree::MTFn, ()>>> {
|
||||
let path = self.path();
|
||||
let c_rc = std::rc::Rc::new(self);
|
||||
let f = dbus::tree::Factory::new_fn::<()>();
|
||||
let mut tree = f.tree(());
|
||||
|
||||
let c_rc1 = c_rc.clone();
|
||||
let conn_iface = telepathy::connection_server(&f, (), move |_| c_rc1.clone());
|
||||
|
||||
let c_rc2 = c_rc.clone();
|
||||
let avatars_iface =
|
||||
telepathy::connection_interface_avatars_server(&f, (), move |_| c_rc2.clone());
|
||||
|
||||
let c_rc3 = c_rc.clone();
|
||||
let contacts_iface =
|
||||
telepathy::connection_interface_contacts_server(&f, (), move |_| c_rc3.clone());
|
||||
|
||||
let _c_rc4 = c_rc.clone();
|
||||
let contact_list_iface =
|
||||
telepathy::connection_interface_contact_list_server(&f, (), move |_| _c_rc4.clone());
|
||||
|
||||
let c_rc5 = c_rc.clone();
|
||||
let requests_iface =
|
||||
telepathy::connection_interface_requests_server(&f, (), move |_| c_rc5.clone());
|
||||
|
||||
let simple_presence_iface =
|
||||
telepathy::connection_interface_simple_presence_server(&f, (), move |_| c_rc.clone());
|
||||
|
||||
tree = tree.add(
|
||||
f.object_path(path, ())
|
||||
.introspectable()
|
||||
.add(conn_iface)
|
||||
.add(avatars_iface)
|
||||
.add(contacts_iface)
|
||||
.add(contact_list_iface)
|
||||
.add(requests_iface)
|
||||
.add(simple_presence_iface),
|
||||
);
|
||||
tree = tree.add(f.object_path("/", ()).introspectable());
|
||||
|
||||
Arc::new(Mutex::new(tree))
|
||||
}
|
||||
}
|
||||
|
107
src/padfoot/connection/avatars.rs
Normal file
107
src/padfoot/connection/avatars.rs
Normal file
@@ -0,0 +1,107 @@
|
||||
use crate::telepathy;
|
||||
|
||||
use dbus::tree::MethodErr;
|
||||
use std::collections::HashMap;
|
||||
|
||||
use super::Connection;
|
||||
|
||||
impl AsRef<dyn telepathy::ConnectionInterfaceAvatars + 'static> for std::rc::Rc<Connection> {
|
||||
fn as_ref(&self) -> &(dyn telepathy::ConnectionInterfaceAvatars + 'static) {
|
||||
&**self
|
||||
}
|
||||
}
|
||||
|
||||
type AvatarRequirementSpec = (Vec<String>, u16, u16, u16, u16, u32);
|
||||
|
||||
// TODO: come back and do this properly, I'm just putting it in for consistency
|
||||
impl telepathy::ConnectionInterfaceAvatars for Connection {
|
||||
fn get_avatar_requirements(&self) -> Result<AvatarRequirementSpec, MethodErr> {
|
||||
println!("Connection<{}>::get_avatar_requirements()", self.id());
|
||||
Ok((vec![], 0, 0, 0, 0, 0))
|
||||
}
|
||||
|
||||
fn get_avatar_tokens(&self, contacts: Vec<u32>) -> Result<Vec<String>, MethodErr> {
|
||||
println!(
|
||||
"Connection<{}>::get_avatar_tokens({:?})",
|
||||
self.id(),
|
||||
contacts
|
||||
);
|
||||
Ok(vec![])
|
||||
}
|
||||
|
||||
fn get_known_avatar_tokens(
|
||||
&self,
|
||||
contacts: Vec<u32>,
|
||||
) -> Result<::std::collections::HashMap<u32, String>, MethodErr> {
|
||||
println!(
|
||||
"Connection<{}>::get_known_avatar_tokens({:?})",
|
||||
self.id(),
|
||||
contacts
|
||||
);
|
||||
Ok(HashMap::<u32, String>::new())
|
||||
}
|
||||
|
||||
fn request_avatar(&self, contact: u32) -> Result<(Vec<u8>, String), MethodErr> {
|
||||
println!("Connection<{}>::request_avatar({})", self.id(), contact);
|
||||
Ok((vec![], "".to_string()))
|
||||
}
|
||||
|
||||
fn request_avatars(&self, contacts: Vec<u32>) -> Result<(), MethodErr> {
|
||||
println!("Connection<{}>::request_avatar({:?})", self.id(), contacts);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_avatar(&self, _avatar: Vec<u8>, mimetype: &str) -> Result<String, MethodErr> {
|
||||
println!(
|
||||
"Connection<{}>::set_avatar((data), {:?})",
|
||||
self.id(),
|
||||
mimetype
|
||||
);
|
||||
Ok("".to_string())
|
||||
}
|
||||
|
||||
fn clear_avatar(&self) -> Result<(), MethodErr> {
|
||||
println!("Connection<{}>::clear_avatar()", self.id());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn supported_avatar_mimetypes(&self) -> Result<Vec<String>, MethodErr> {
|
||||
println!("Connection<{}>::supported_avatar_mimetypes()", self.id());
|
||||
Ok(vec![])
|
||||
}
|
||||
|
||||
fn minimum_avatar_height(&self) -> Result<u32, MethodErr> {
|
||||
println!("Connection<{}>::minimum_avatar_height()", self.id());
|
||||
Ok(0)
|
||||
}
|
||||
|
||||
fn minimum_avatar_width(&self) -> Result<u32, MethodErr> {
|
||||
println!("Connection<{}>::minimum_avatar_width()", self.id());
|
||||
Ok(0)
|
||||
}
|
||||
|
||||
fn recommended_avatar_height(&self) -> Result<u32, MethodErr> {
|
||||
println!("Connection<{}>::recommended_avatar_height()", self.id());
|
||||
Ok(0)
|
||||
}
|
||||
|
||||
fn recommended_avatar_width(&self) -> Result<u32, MethodErr> {
|
||||
println!("Connection<{}>::recommended_avatar_width()", self.id());
|
||||
Ok(0)
|
||||
}
|
||||
|
||||
fn maximum_avatar_height(&self) -> Result<u32, MethodErr> {
|
||||
println!("Connection<{}>::maximum_avatar_height()", self.id());
|
||||
Ok(0)
|
||||
}
|
||||
|
||||
fn maximum_avatar_width(&self) -> Result<u32, MethodErr> {
|
||||
println!("Connection<{}>::maximum_avatar_width()", self.id());
|
||||
Ok(0)
|
||||
}
|
||||
|
||||
fn maximum_avatar_bytes(&self) -> Result<u32, MethodErr> {
|
||||
println!("Connection<{}>::maximum_avatar_bytes()", self.id());
|
||||
Ok(0)
|
||||
}
|
||||
}
|
354
src/padfoot/connection/connection.rs
Normal file
354
src/padfoot/connection/connection.rs
Normal file
@@ -0,0 +1,354 @@
|
||||
use crate::telepathy;
|
||||
use crate::telepathy::{ConnectionInterfaceContacts, ConnectionInterfaceRequests}; // Non-deprecated channel methods
|
||||
|
||||
use async_std::task::block_on;
|
||||
use dbus::message::SignalArgs;
|
||||
use dbus::tree::MethodErr;
|
||||
use dc::contact::Contact;
|
||||
use deltachat as dc;
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::convert::TryInto;
|
||||
use std::thread;
|
||||
|
||||
use super::{Connection, DbusAction};
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum ConnState {
|
||||
Initial,
|
||||
Connected,
|
||||
Disconnected,
|
||||
}
|
||||
|
||||
type Result<T> = std::result::Result<T, dbus::tree::MethodErr>;
|
||||
|
||||
// Deprecated channel information. Replaced by Requests.ChannelSpec
|
||||
type ChannelInfo = (
|
||||
dbus::Path<'static>, // Object path
|
||||
String, // Channel type
|
||||
u32, // Handle type
|
||||
u32, // Handle
|
||||
);
|
||||
|
||||
type ContactSubscription = (
|
||||
u32, // Subscribe state
|
||||
u32, // Publish state
|
||||
String, // Publish-request message
|
||||
);
|
||||
|
||||
pub fn connection_interfaces() -> Vec<String> {
|
||||
vec![
|
||||
"org.freedesktop.Telepathy.Connection".to_string(),
|
||||
"org.freedesktop.Telepathy.Connection.Interface.Avatars".to_string(),
|
||||
"org.freedesktop.Telepathy.Connection.Interface.Contacts".to_string(),
|
||||
"org.freedesktop.Telepathy.Connection.Interface.ContactList".to_string(),
|
||||
"org.freedesktop.Telepathy.Connection.Interface.Requests".to_string(),
|
||||
"org.freedesktop.Telepathy.Connection.Interface.SimplePresence".to_string(),
|
||||
]
|
||||
}
|
||||
|
||||
impl AsRef<dyn telepathy::Connection + 'static> for std::rc::Rc<Connection> {
|
||||
fn as_ref(&self) -> &(dyn telepathy::Connection + 'static) {
|
||||
&**self
|
||||
}
|
||||
}
|
||||
|
||||
impl telepathy::Connection for Connection {
|
||||
// In connect(), we start the threads that drive the deltachat context
|
||||
fn connect(&self) -> Result<()> {
|
||||
println!("Connection<{}>::connect()", self.id());
|
||||
|
||||
let io_ctx = self.ctx.clone();
|
||||
let io_id = self.id();
|
||||
let _io_thread = thread::spawn(move || {
|
||||
block_on(io_ctx.start_io());
|
||||
|
||||
println!("Connection<{}>::connect(): I/O thread exited", io_id);
|
||||
});
|
||||
|
||||
// Just pretend to be connected all the time for now. Tracking IMAP+SMTP
|
||||
// state is a pain
|
||||
let state = self.state.clone();
|
||||
let mut w = state.write().unwrap();
|
||||
*w = ConnState::Connected;
|
||||
let ctx = self.ctx.clone();
|
||||
|
||||
// Emit a StatusChanged signal for the benefit of others, but the caller
|
||||
// learns from our RPC response
|
||||
let connected_sig = telepathy::ConnectionStatusChanged {
|
||||
status: 0, // Connected
|
||||
reason: 1, // Requested
|
||||
}
|
||||
.to_emit_message(&self.path());
|
||||
|
||||
self.actq.send(DbusAction::Signal(connected_sig)).unwrap();
|
||||
self.actq.send(DbusAction::FreshMessages).unwrap();
|
||||
|
||||
// If we can, emit signals on connect about the contact list
|
||||
if let Ok(handles) = block_on(Contact::get_all(
|
||||
&ctx,
|
||||
(dc::constants::DC_GCL_ADD_SELF as usize)
|
||||
.try_into()
|
||||
.unwrap(),
|
||||
None::<String>,
|
||||
)) {
|
||||
println!(" HANDLES: {:?}", handles);
|
||||
let mut changes = HashMap::<u32, ContactSubscription>::new();
|
||||
for handle in handles {
|
||||
println!(" *** Handle: {}", handle);
|
||||
changes.insert(handle, (4, 4, "".to_string())); // FIXME: hardcoded lies
|
||||
}
|
||||
|
||||
// TODO: the old signal is deprecated. The new signal requires us to
|
||||
// send identifiers with it, which is a bit of a lookup here.
|
||||
// let cl_sig_new = telepathy::ConnectionInterfaceContactsChangedWithID {
|
||||
// }.to_emit_message(&self.path());
|
||||
|
||||
let cl_sig_old = telepathy::ConnectionInterfaceContactListContactsChanged {
|
||||
changes,
|
||||
removals: Vec::new(),
|
||||
}
|
||||
.to_emit_message(&self.path());
|
||||
|
||||
// self.actq.send(DbusAction::Signal(cl_sig_new)).unwrap();
|
||||
self.actq.send(DbusAction::Signal(cl_sig_old)).unwrap();
|
||||
};
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn disconnect(&self) -> Result<()> {
|
||||
println!("Connection<{}>::disconnect()", self.id());
|
||||
block_on(self.ctx.stop_io());
|
||||
|
||||
let state = self.state.clone();
|
||||
let mut w = state.write().unwrap();
|
||||
*w = ConnState::Disconnected;
|
||||
|
||||
// FIXME: we need to signal to the CM that they should remove the
|
||||
// connection from the active list
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn interfaces(&self) -> Result<Vec<String>> {
|
||||
println!("Connection<{}>::interfaces()", self.id());
|
||||
|
||||
self.get_interfaces()
|
||||
}
|
||||
|
||||
fn get_interfaces(&self) -> Result<Vec<String>> {
|
||||
println!("Connection<{}>::get_interfaces()", self.id());
|
||||
|
||||
Ok(connection_interfaces())
|
||||
}
|
||||
|
||||
fn get_protocol(&self) -> Result<String> {
|
||||
println!("Connection<{}>::get_protocol()", self.id());
|
||||
|
||||
Ok(crate::padfoot::PROTO_NAME.to_string())
|
||||
}
|
||||
|
||||
fn self_handle(&self) -> Result<u32> {
|
||||
println!("Connection<{}>::self_handle()", self.id());
|
||||
|
||||
Ok(dc::constants::DC_CONTACT_ID_SELF)
|
||||
}
|
||||
|
||||
// Deprecated in favour of the self_handle DBUS property
|
||||
fn get_self_handle(&self) -> Result<u32> {
|
||||
println!("Connection<{}>::get_self_handle()", self.id());
|
||||
|
||||
self.self_handle()
|
||||
}
|
||||
|
||||
fn status(&self) -> Result<u32> {
|
||||
println!("Connection<{}>::status()", self.id());
|
||||
|
||||
self.get_status()
|
||||
}
|
||||
|
||||
fn get_status(&self) -> Result<u32> {
|
||||
match *self.state.clone().read().unwrap() {
|
||||
ConnState::Initial | ConnState::Disconnected => Ok(2),
|
||||
ConnState::Connected => Ok(0),
|
||||
}
|
||||
}
|
||||
|
||||
fn hold_handles(&self, handle_type: u32, handles: Vec<u32>) -> Result<()> {
|
||||
println!(
|
||||
"Connection<{}>::hold_handles({}, {:?})",
|
||||
self.id(),
|
||||
handle_type,
|
||||
handles
|
||||
);
|
||||
|
||||
// Since HasImmortalHandles is true, this doesn't need to do anything
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn inspect_handles(&self, handle_type: u32, handles: Vec<u32>) -> Result<Vec<String>> {
|
||||
println!(
|
||||
"Connection<{}>::inspect_handles({}, {:?})",
|
||||
self.id(),
|
||||
handle_type,
|
||||
handles
|
||||
);
|
||||
match handle_type {
|
||||
crate::padfoot::HANDLE_TYPE_CONTACT => {
|
||||
let mut out = Vec::<String>::new();
|
||||
for (_handle, attrs) in self.get_contact_attributes(handles, vec![], true)? {
|
||||
if let Some(contact_id) =
|
||||
attrs.get("org.freedesktop.Telepathy.Connection/contact-id")
|
||||
{
|
||||
out.push(contact_id.0.as_str().unwrap().to_string());
|
||||
} else {
|
||||
return Err(MethodErr::no_arg()); // FIXME: should be InvalidHandle
|
||||
}
|
||||
}
|
||||
Ok(out)
|
||||
}
|
||||
_ => Err(MethodErr::no_arg()), // FIXME: should be NotImplemented?
|
||||
}
|
||||
}
|
||||
|
||||
// Deprecated in favour of Requests.Channels
|
||||
fn list_channels(&self) -> Result<Vec<ChannelInfo>> {
|
||||
println!("Connection<{}>::list_channels()", self.id());
|
||||
|
||||
let data = self.channels()?;
|
||||
let mut out = Vec::<ChannelInfo>::new();
|
||||
|
||||
for (path, _props) in data {
|
||||
// FIXME: pull correct details from the properties hash
|
||||
out.push((
|
||||
path,
|
||||
"org.freedesktop.Telepathy.Channel.Type.Text".to_string(),
|
||||
0,
|
||||
0,
|
||||
))
|
||||
}
|
||||
|
||||
Ok(out)
|
||||
}
|
||||
|
||||
fn release_handles(&self, handle_type: u32, handles: Vec<u32>) -> Result<()> {
|
||||
println!(
|
||||
"Connection<{}>::release_handles({}, {:?})",
|
||||
self.id(),
|
||||
handle_type,
|
||||
handles
|
||||
);
|
||||
|
||||
// Since HasImmortalHandles is true, we don't need to do anything
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// RequestChannel is deprecated in favour of the Requests interface.
|
||||
fn request_channel(
|
||||
&self,
|
||||
channel_type: &str,
|
||||
handle_type: u32,
|
||||
handle: u32,
|
||||
suppress_handler: bool,
|
||||
) -> Result<dbus::Path<'static>> {
|
||||
println!(
|
||||
"Connection<{}>::request_channel({}, {}, {}, {})",
|
||||
self.id(),
|
||||
channel_type,
|
||||
handle_type,
|
||||
handle,
|
||||
suppress_handler
|
||||
);
|
||||
|
||||
Err(MethodErr::no_arg()) // FIXME: should be NotImplemented?
|
||||
}
|
||||
|
||||
fn request_handles(&self, handle_type: u32, identifiers: Vec<&str>) -> Result<Vec<u32>> {
|
||||
println!(
|
||||
"Connection<{}>::request_handles({}, {:?})",
|
||||
self.id(),
|
||||
handle_type,
|
||||
identifiers
|
||||
);
|
||||
|
||||
match handle_type {
|
||||
crate::padfoot::HANDLE_TYPE_CONTACT => {
|
||||
let ctx = &self.ctx;
|
||||
let mut out = Vec::<u32>::new();
|
||||
|
||||
// Identifiers is a list of email addresses. These can be
|
||||
// contact IDs, although we may have to create contacts to get
|
||||
// the ID.
|
||||
//
|
||||
// FIXME: will it be faster to get all and filter?
|
||||
|
||||
for addr in identifiers {
|
||||
let id = block_on(Contact::lookup_id_by_addr(
|
||||
ctx,
|
||||
addr,
|
||||
dc::contact::Origin::Unknown,
|
||||
));
|
||||
match id {
|
||||
0 => {
|
||||
// No contact exists for this address yet. Try to
|
||||
// add one so we can have an ID.
|
||||
match block_on(Contact::create(ctx, &addr, &addr)) {
|
||||
Ok(new_id) => out.push(new_id),
|
||||
Err(e) => {
|
||||
println!("Failed to add contact {}: {}", addr, e);
|
||||
return Err(MethodErr::no_arg());
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => out.push(id),
|
||||
}
|
||||
}
|
||||
|
||||
Ok(out)
|
||||
}
|
||||
_ => Err(MethodErr::no_arg()), // FIXME: should be NotImplemented?
|
||||
}
|
||||
}
|
||||
|
||||
fn add_client_interest(&self, tokens: Vec<&str>) -> Result<()> {
|
||||
println!(
|
||||
"Connection<{}>::add_client_interest({:?})",
|
||||
self.id(),
|
||||
tokens
|
||||
);
|
||||
Err(MethodErr::no_arg()) // FIXME: should be NotImplemented?
|
||||
}
|
||||
|
||||
fn remove_client_interest(&self, tokens: Vec<&str>) -> Result<()> {
|
||||
println!(
|
||||
"Connection<{}>::remove_client_interest({:?})",
|
||||
self.id(),
|
||||
tokens
|
||||
);
|
||||
Err(MethodErr::no_arg()) // FIXME: should be NotImplemented?
|
||||
}
|
||||
|
||||
fn self_id(&self) -> Result<String> {
|
||||
println!("Connection<{}>::self_id()", self.id());
|
||||
|
||||
let contact = match block_on(dc::contact::Contact::get_by_id(
|
||||
&self.ctx,
|
||||
dc::constants::DC_CONTACT_ID_SELF,
|
||||
)) {
|
||||
Ok(c) => c,
|
||||
Err(e) => {
|
||||
println!(" err: {}", e);
|
||||
return Err(MethodErr::no_arg());
|
||||
}
|
||||
};
|
||||
|
||||
Ok(contact.get_addr().to_string())
|
||||
}
|
||||
|
||||
fn has_immortal_handles(&self) -> Result<bool> {
|
||||
println!("Connection<{}>::has_immortal_handles()", self.id());
|
||||
|
||||
Ok(true)
|
||||
}
|
||||
}
|
129
src/padfoot/connection/contact_list.rs
Normal file
129
src/padfoot/connection/contact_list.rs
Normal file
@@ -0,0 +1,129 @@
|
||||
use crate::padfoot::VarArg;
|
||||
use crate::telepathy;
|
||||
|
||||
use async_std::task::block_on;
|
||||
use dbus::tree::MethodErr;
|
||||
use dc::constants::DC_GCL_ADD_SELF;
|
||||
use dc::contact::Contact;
|
||||
use deltachat as dc;
|
||||
use std::collections::HashMap;
|
||||
use std::convert::TryInto;
|
||||
use telepathy::ConnectionInterfaceContacts; // for get_contact_attributes
|
||||
|
||||
use super::Connection;
|
||||
|
||||
impl AsRef<dyn telepathy::ConnectionInterfaceContactList + 'static> for std::rc::Rc<Connection> {
|
||||
fn as_ref(&self) -> &(dyn telepathy::ConnectionInterfaceContactList + 'static) {
|
||||
&**self
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME: come back and do this properly later
|
||||
impl telepathy::ConnectionInterfaceContactList for Connection {
|
||||
fn get_contact_list_attributes(
|
||||
&self,
|
||||
interfaces: Vec<&str>,
|
||||
hold: bool,
|
||||
) -> Result<HashMap<u32, HashMap<String, VarArg>>, MethodErr> {
|
||||
println!(
|
||||
"Connection<{}>::get_contact_list_attributes({:?}, {})",
|
||||
self.id(),
|
||||
interfaces,
|
||||
hold
|
||||
);
|
||||
|
||||
let ids = match block_on(Contact::get_all(
|
||||
&self.ctx,
|
||||
DC_GCL_ADD_SELF.try_into().unwrap(),
|
||||
None::<String>,
|
||||
)) {
|
||||
Ok(c) => c,
|
||||
Err(e) => {
|
||||
println!(" err: {}", e);
|
||||
return Err(MethodErr::no_arg());
|
||||
}
|
||||
};
|
||||
|
||||
// FIXME: swap implementations so Contacts depends on ContactList?
|
||||
self.get_contact_attributes(ids, vec![], true)
|
||||
}
|
||||
|
||||
fn request_subscription(&self, contacts: Vec<u32>, message: &str) -> Result<(), MethodErr> {
|
||||
println!(
|
||||
"Connection<{}>::request_subscription({:?}, {})",
|
||||
self.id(),
|
||||
contacts,
|
||||
message
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn authorize_publication(&self, contacts: Vec<u32>) -> Result<(), MethodErr> {
|
||||
println!(
|
||||
"Connection<{}>::authorize_publication({:?})",
|
||||
self.id(),
|
||||
contacts
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
fn remove_contacts(&self, contacts: Vec<u32>) -> Result<(), MethodErr> {
|
||||
println!("Connection<{}>::remove_contacts({:?})", self.id(), contacts);
|
||||
|
||||
for contact_id in contacts {
|
||||
// FIXME: don't ignore errors
|
||||
if let Err(e) = block_on(Contact::delete(&self.ctx, contact_id)) {
|
||||
println!(" Deleting contact {} failed: {}", contact_id, e);
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME: signals MUST be emitted before this method returns
|
||||
// FIXME: emitting signals at all would be great
|
||||
|
||||
Ok(())
|
||||
}
|
||||
fn unsubscribe(&self, contacts: Vec<u32>) -> Result<(), MethodErr> {
|
||||
println!("Connection<{}>::unsubscribe({:?})", self.id(), contacts);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn unpublish(&self, contacts: Vec<u32>) -> Result<(), MethodErr> {
|
||||
println!("Connection<{}>::unpublish({:?})", self.id(), contacts);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn download(&self) -> Result<(), MethodErr> {
|
||||
println!("Connection<{}>::download()", self.id());
|
||||
|
||||
// This can be a no-op since we store the contacts list in delta's DB
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn contact_list_state(&self) -> Result<u32, MethodErr> {
|
||||
println!("Connection<{}>::contact_list_state()", self.id());
|
||||
Ok(3) // Success
|
||||
}
|
||||
|
||||
fn contact_list_persists(&self) -> Result<bool, MethodErr> {
|
||||
println!("Connection<{}>::contact_list_persists()", self.id());
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
fn can_change_contact_list(&self) -> Result<bool, MethodErr> {
|
||||
println!("Connection<{}>::can_change_contact_list()", self.id());
|
||||
Ok(true)
|
||||
}
|
||||
fn request_uses_message(&self) -> Result<bool, MethodErr> {
|
||||
println!("Connection<{}>::request_uses_message()", self.id());
|
||||
Ok(false)
|
||||
}
|
||||
|
||||
fn download_at_connection(&self) -> Result<bool, MethodErr> {
|
||||
println!("Connection<{}>::download_at_connection()", self.id());
|
||||
|
||||
// TODO: https://telepathy.freedesktop.org/spec/Connection_Interface_Contact_List.html#Property:DownloadAtConnection
|
||||
// Connections ... SHOULD provide a corresponding parameter named
|
||||
// org.freedesktop.Telepathy.Connection.Interface.ContactList.DownloadAtConnection
|
||||
// with the DBus_Property flag
|
||||
Ok(true)
|
||||
}
|
||||
}
|
108
src/padfoot/connection/contacts.rs
Normal file
108
src/padfoot/connection/contacts.rs
Normal file
@@ -0,0 +1,108 @@
|
||||
use crate::padfoot::{var_str, var_u32, VarArg};
|
||||
use crate::telepathy;
|
||||
|
||||
use async_std::task::block_on;
|
||||
use dbus::tree::MethodErr;
|
||||
use deltachat::contact::{Contact, Origin};
|
||||
use std::collections::HashMap;
|
||||
|
||||
use super::Connection;
|
||||
|
||||
impl AsRef<dyn telepathy::ConnectionInterfaceContacts + 'static> for std::rc::Rc<Connection> {
|
||||
fn as_ref(&self) -> &(dyn telepathy::ConnectionInterfaceContacts + 'static) {
|
||||
&**self
|
||||
}
|
||||
}
|
||||
|
||||
impl telepathy::ConnectionInterfaceContacts for Connection {
|
||||
fn get_contact_attributes(
|
||||
&self,
|
||||
handles: Vec<u32>,
|
||||
interfaces: Vec<&str>,
|
||||
hold: bool,
|
||||
) -> Result<HashMap<u32, HashMap<String, VarArg>>, MethodErr> {
|
||||
println!(
|
||||
"Connection<{}>::get_contact_attributes({:?}, {:?}, {})",
|
||||
self.id(),
|
||||
handles,
|
||||
interfaces,
|
||||
hold
|
||||
);
|
||||
|
||||
let mut out = HashMap::<u32, HashMap<String, VarArg>>::new();
|
||||
for id in handles.iter() {
|
||||
// FIXME: work out how to use get_all
|
||||
let contact = match block_on(Contact::get_by_id(&self.ctx, *id)) {
|
||||
Ok(c) => c,
|
||||
Err(_e) => continue, // Invalid IDs are silently ignored
|
||||
};
|
||||
|
||||
let mut props = HashMap::<String, VarArg>::new();
|
||||
// This is mandatory
|
||||
props.insert(
|
||||
"org.freedesktop.Telepathy.Connection/contact-id".to_string(),
|
||||
var_str(contact.get_addr().to_string()),
|
||||
);
|
||||
|
||||
// The empty string means "no avatar"
|
||||
props.insert(
|
||||
"org.freedesktop.Telepathy.Connection.Interface.Avatars/token".to_string(),
|
||||
var_str("".to_string()),
|
||||
);
|
||||
|
||||
props.insert(
|
||||
"org.freedesktop.Telepathy.Connection.Interface.ContactList/publish".to_string(),
|
||||
var_u32(4), // YES (faking it for now)
|
||||
);
|
||||
|
||||
props.insert(
|
||||
"org.freedesktop.Telepathy.Connection.Interface.ContactList/subscribe".to_string(),
|
||||
var_u32(4), // YES (faking it for now)
|
||||
);
|
||||
|
||||
out.insert(*id, props);
|
||||
}
|
||||
|
||||
Ok(out)
|
||||
}
|
||||
|
||||
fn get_contact_by_id(
|
||||
&self,
|
||||
identifier: &str,
|
||||
interfaces: Vec<&str>,
|
||||
) -> Result<(u32, HashMap<String, VarArg>), MethodErr> {
|
||||
println!(
|
||||
"Connection<{}>::get_contact_by_id({}, {:?})",
|
||||
self.id(),
|
||||
identifier,
|
||||
interfaces
|
||||
);
|
||||
|
||||
let id = block_on(Contact::lookup_id_by_addr(
|
||||
&self.ctx,
|
||||
identifier,
|
||||
Origin::Unknown,
|
||||
));
|
||||
|
||||
if id == 0 {
|
||||
return Err(MethodErr::no_arg()); // FIXME: should be InvalidHandle
|
||||
};
|
||||
|
||||
let mut contacts = self.get_contact_attributes(vec![id], interfaces, true)?;
|
||||
if let Some(contact) = contacts.remove(&id) {
|
||||
Ok((id, contact))
|
||||
} else {
|
||||
Err(MethodErr::no_arg()) // FIXME: should be InvalidHandle
|
||||
}
|
||||
}
|
||||
|
||||
fn contact_attribute_interfaces(&self) -> Result<Vec<String>, MethodErr> {
|
||||
println!("Connection<{}>::contact_attribute_interfaces()", self.id());
|
||||
|
||||
Ok(vec![
|
||||
"org.freedesktop.Telepathy.Connection".to_string(),
|
||||
"org.freedesktop.Telepathy.Connection.Interface.Avatars".to_string(),
|
||||
"org.freedesktop.Telepathy.Connection.Interface.ContactList".to_string(),
|
||||
])
|
||||
}
|
||||
}
|
52
src/padfoot/connection/escape.rs
Normal file
52
src/padfoot/connection/escape.rs
Normal file
@@ -0,0 +1,52 @@
|
||||
fn escape_one(b: u8) -> String {
|
||||
format!("_{:0<2x}", b)
|
||||
}
|
||||
|
||||
// Some non-empty sequence of ASCII letters, digits and underscores
|
||||
pub fn escape(s: String) -> String {
|
||||
// Special-case the empty string
|
||||
if s.is_empty() {
|
||||
return "_".to_string();
|
||||
}
|
||||
|
||||
let bytes = s.into_bytes();
|
||||
let mut iter = bytes.iter();
|
||||
let mut out = String::new();
|
||||
|
||||
// Only alphanumeric in the first byte
|
||||
let x = *iter.next().expect("Already checked len > 0");
|
||||
let first = match x {
|
||||
b'a'..=b'z' | b'A'..=b'Z' => unsafe { String::from_utf8_unchecked(vec![x]) },
|
||||
_ => escape_one(x),
|
||||
};
|
||||
|
||||
out.push_str(&first);
|
||||
|
||||
for x in iter {
|
||||
let next = match x {
|
||||
b'a'..=b'z' | b'A'..=b'Z' | b'0'..=b'9' => unsafe {
|
||||
String::from_utf8_unchecked(vec![*x])
|
||||
},
|
||||
_ => escape_one(*x),
|
||||
};
|
||||
|
||||
out.push_str(&next);
|
||||
}
|
||||
|
||||
out
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_escape() {
|
||||
assert_eq!(escape("".to_string()), "_");
|
||||
assert_eq!(escape("foo".to_string()), "foo");
|
||||
assert_eq!(escape("foo@bar".to_string()), "foo_40bar");
|
||||
assert_eq!(escape("foo_bar".to_string()), "foo_5fbar");
|
||||
assert_eq!(escape("foo__@__bar".to_string()), "foo_5f_5f_40_5f_5fbar");
|
||||
assert_eq!(escape("1foo".to_string()), "_31foo");
|
||||
}
|
||||
}
|
137
src/padfoot/connection/requests.rs
Normal file
137
src/padfoot/connection/requests.rs
Normal file
@@ -0,0 +1,137 @@
|
||||
use crate::padfoot::{get_var_str, get_var_u32, requestables, Channel, DbusAction, VarArg};
|
||||
use crate::telepathy;
|
||||
|
||||
use async_std::task::block_on;
|
||||
use dbus::tree::MethodErr;
|
||||
use dc::contact::Contact;
|
||||
use deltachat as dc;
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
use super::Connection;
|
||||
|
||||
type ChannelSpec = (
|
||||
dbus::Path<'static>, // Object path on this connection
|
||||
HashMap<String, VarArg>, // Map of channel property -> value
|
||||
);
|
||||
|
||||
type RequestableChannelSpec = (HashMap<String, VarArg>, Vec<String>);
|
||||
|
||||
type Result<T> = std::result::Result<T, MethodErr>;
|
||||
|
||||
impl AsRef<dyn telepathy::ConnectionInterfaceRequests + 'static> for std::rc::Rc<Connection> {
|
||||
fn as_ref(&self) -> &(dyn telepathy::ConnectionInterfaceRequests + 'static) {
|
||||
&**self
|
||||
}
|
||||
}
|
||||
|
||||
impl telepathy::ConnectionInterfaceRequests for Connection {
|
||||
fn create_channel(
|
||||
&self,
|
||||
request: HashMap<&str, VarArg>,
|
||||
) -> Result<(dbus::Path<'static>, HashMap<String, VarArg>)> {
|
||||
println!("Connection<{}>::create_channel({:?})", self.id(), request);
|
||||
|
||||
Err(MethodErr::no_arg()) // FIXME: should be NotImplemented?
|
||||
}
|
||||
|
||||
fn ensure_channel(
|
||||
&self,
|
||||
request: HashMap<&str, VarArg>,
|
||||
) -> Result<(bool, dbus::Path<'static>, HashMap<String, VarArg>)> {
|
||||
let path = self.path().clone();
|
||||
println!("Connection<{}>::ensure_channel({:?})", self.id(), request);
|
||||
|
||||
// Empathy sends this for the request:
|
||||
//
|
||||
// {
|
||||
// "org.freedesktop.Telepathy.Channel.TargetID": Variant("me@example.com"),
|
||||
// "org.freedesktop.Telepathy.Channel.TargetHandleType": Variant(1),
|
||||
// "org.freedesktop.Telepathy.Channel.ChannelType": Variant("org.freedesktop.Telepathy.Channel.Type.Text")
|
||||
// }
|
||||
|
||||
// FIXME: life would be easier with TargetHandle
|
||||
let target_id = request
|
||||
.get("org.freedesktop.Telepathy.Channel.TargetID")
|
||||
.map(|va| get_var_str(va))
|
||||
.unwrap();
|
||||
|
||||
let target_handle_type = request
|
||||
.get("org.freedesktop.Telepathy.Channel.TargetHandleType")
|
||||
.map(|va| get_var_u32(va))
|
||||
.unwrap();
|
||||
|
||||
let channel_type = request
|
||||
.get("org.freedesktop.Telepathy.Channel.ChannelType")
|
||||
.map(|va| get_var_str(va))
|
||||
.unwrap();
|
||||
|
||||
// Text only
|
||||
if channel_type != "org.freedesktop.Telepathy.Channel.Type.Text" {
|
||||
println!(" Wrong channel type: {:?}", channel_type);
|
||||
return Err(MethodErr::no_arg());
|
||||
};
|
||||
|
||||
// IMs only
|
||||
if target_handle_type != 1 {
|
||||
println!(" Wrong target handle type: {:?}", target_handle_type);
|
||||
return Err(MethodErr::no_arg());
|
||||
};
|
||||
|
||||
let target_handle = block_on(Contact::lookup_id_by_addr(
|
||||
&self.ctx,
|
||||
target_id.clone(),
|
||||
dc::contact::Origin::Unknown,
|
||||
));
|
||||
if target_handle == 0 {
|
||||
println!("Couldn't find target handle for {}", target_id);
|
||||
return Err(MethodErr::no_arg());
|
||||
};
|
||||
|
||||
let chat_id = dc::chat::ChatId::new(target_handle);
|
||||
let channel_path = Connection::build_channel_path(path, chat_id);
|
||||
|
||||
// Return an existing channel if it already exists
|
||||
let chans = self.channels.read().unwrap();
|
||||
if let Some(channel) = chans.get(&channel_path) {
|
||||
return Ok((false, channel_path, channel.chan_props()));
|
||||
}
|
||||
|
||||
// Now we need to discover or create a chat id for the contact
|
||||
let chat_id = block_on(dc::chat::create_by_contact_id(&self.ctx, target_handle)).unwrap();
|
||||
let channel = Channel::new(
|
||||
self.actq.clone(),
|
||||
chat_id,
|
||||
self.ctx.clone(),
|
||||
dc::constants::DC_CONTACT_ID_SELF, // initiator is self in this case
|
||||
channel_path.clone(),
|
||||
true, // requested
|
||||
target_handle,
|
||||
);
|
||||
let response = channel.chan_props(); // FIXME: fill with data about the channel
|
||||
|
||||
// Send signal
|
||||
self.actq.send(DbusAction::NewChannel(channel)).unwrap();
|
||||
|
||||
Ok((true, channel_path, response))
|
||||
}
|
||||
|
||||
fn channels(&self) -> Result<Vec<ChannelSpec>> {
|
||||
println!("Connection<{}>::channels()", self.id());
|
||||
|
||||
let mut out = Vec::<ChannelSpec>::new();
|
||||
for channel in self.channels.read().unwrap().values() {
|
||||
out.push((
|
||||
channel.path(),
|
||||
HashMap::<String, VarArg>::new(), // FIXME: work out what props should be shown
|
||||
));
|
||||
}
|
||||
|
||||
Ok(out)
|
||||
}
|
||||
|
||||
fn requestable_channel_classes(&self) -> Result<Vec<RequestableChannelSpec>> {
|
||||
println!("Connection<{}>::requestable_channel_classes()", self.id());
|
||||
Ok(requestables())
|
||||
}
|
||||
}
|
60
src/padfoot/connection/simple_presence.rs
Normal file
60
src/padfoot/connection/simple_presence.rs
Normal file
@@ -0,0 +1,60 @@
|
||||
use crate::padfoot::{statuses, SimpleStatusSpec};
|
||||
use crate::telepathy;
|
||||
|
||||
use dbus::tree::MethodErr;
|
||||
use std::collections::HashMap;
|
||||
|
||||
use super::Connection;
|
||||
|
||||
pub type SimplePresenceSpec = (
|
||||
u32, // connection presence type
|
||||
String, // status
|
||||
String, // status message
|
||||
);
|
||||
|
||||
impl AsRef<dyn telepathy::ConnectionInterfaceSimplePresence + 'static> for std::rc::Rc<Connection> {
|
||||
fn as_ref(&self) -> &(dyn telepathy::ConnectionInterfaceSimplePresence + 'static) {
|
||||
&**self
|
||||
}
|
||||
}
|
||||
|
||||
impl telepathy::ConnectionInterfaceSimplePresence for Connection {
|
||||
fn set_presence(&self, status: &str, status_message: &str) -> Result<(), MethodErr> {
|
||||
println!(
|
||||
"Connection<{}>::set_presence({}, {:?})",
|
||||
self.id(),
|
||||
status,
|
||||
status_message
|
||||
);
|
||||
|
||||
// FIXME: emit a presence changed signal
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_presences(
|
||||
&self,
|
||||
contacts: Vec<u32>,
|
||||
) -> Result<HashMap<u32, SimplePresenceSpec>, MethodErr> {
|
||||
println!("Connection<{}>::get_presences({:?})", self.id(), contacts);
|
||||
|
||||
let mut out = HashMap::<u32, SimplePresenceSpec>::new();
|
||||
|
||||
for id in contacts {
|
||||
out.insert(id, (2, "available".to_string(), "".to_string())); // Available
|
||||
}
|
||||
|
||||
Ok(out)
|
||||
}
|
||||
|
||||
fn statuses(&self) -> Result<HashMap<String, SimpleStatusSpec>, MethodErr> {
|
||||
println!("Connection<{}>::statuses()", self.id());
|
||||
|
||||
Ok(statuses())
|
||||
}
|
||||
|
||||
fn maximum_status_message_length(&self) -> Result<u32, MethodErr> {
|
||||
println!("Connection<{}>::maximum_status_message_length()", self.id());
|
||||
Ok(0)
|
||||
}
|
||||
}
|
@@ -1,16 +1,20 @@
|
||||
use crate::padfoot::{var_arg, var_str, var_str_vec, VarArg};
|
||||
use crate::telepathy;
|
||||
|
||||
use dbus::{arg, message::SignalArgs, tree};
|
||||
use dbus::message::SignalArgs;
|
||||
use dbus::tree::MethodErr;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::sync::mpsc;
|
||||
use std::sync::{mpsc, Arc, Mutex};
|
||||
|
||||
pub const CM_BUS_NAME: &'static str = "org.freedesktop.Telepathy.ConnectionManager.padfoot";
|
||||
pub const CM_CONN_BUS_NAME: &'static str = "org.freedesktop.Telepathy.Connection.padfoot";
|
||||
pub const CM_OBJECT_PATH: &'static str = "/org/freedesktop/Telepathy/ConnectionManager/padfoot";
|
||||
use super::{ConnSettings, Connection};
|
||||
|
||||
pub const CM_BUS_NAME: &str = "org.freedesktop.Telepathy.ConnectionManager.padfoot";
|
||||
pub const CM_CONN_BUS_NAME: &str = "org.freedesktop.Telepathy.Connection.padfoot";
|
||||
pub const CM_OBJECT_PATH: &str = "/org/freedesktop/Telepathy/ConnectionManager/padfoot";
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ConnectionManager {
|
||||
conns: std::sync::Mutex<HashSet<String>>,
|
||||
conns: Arc<Mutex<HashSet<dbus::Path<'static>>>>,
|
||||
sender: mpsc::Sender<dbus::Message>,
|
||||
}
|
||||
|
||||
@@ -22,11 +26,11 @@ impl AsRef<dyn telepathy::ConnectionManager + 'static> for std::rc::Rc<Connectio
|
||||
|
||||
impl ConnectionManager {
|
||||
pub fn new() -> (Self, mpsc::Receiver<dbus::Message>) {
|
||||
let (msg_s, msg_r) = std::sync::mpsc::channel::<dbus::Message>();
|
||||
let (msg_s, msg_r) = mpsc::channel::<dbus::Message>();
|
||||
|
||||
(
|
||||
ConnectionManager {
|
||||
conns: std::sync::Mutex::new(HashSet::<String>::new()),
|
||||
conns: Arc::new(Mutex::new(HashSet::<dbus::Path<'static>>::new())),
|
||||
sender: msg_s,
|
||||
},
|
||||
msg_r,
|
||||
@@ -35,37 +39,49 @@ impl ConnectionManager {
|
||||
|
||||
fn create_connection(
|
||||
&self,
|
||||
params: HashMap<&str, super::Variant>,
|
||||
) -> Result<(String, dbus::Path<'static>), tree::MethodErr> {
|
||||
let conn = super::Connection::new(params)?;
|
||||
let bus = conn.bus();
|
||||
let path = conn.path();
|
||||
let dbus_conn_path = dbus::strings::Path::new(path.to_owned())
|
||||
.expect("Object path should meet DBUS requirements");
|
||||
settings: ConnSettings,
|
||||
) -> Result<(String, dbus::Path<'static>), MethodErr> {
|
||||
let bus = settings.bus();
|
||||
let path = settings.path();
|
||||
|
||||
// If we already have this connection, we can point the user at it.
|
||||
// FIXME: should we update the other params in this case?
|
||||
let mut conns = self.conns.lock().expect("Mutex access");
|
||||
|
||||
// We can't call connect() multiple times on the connection yet
|
||||
if conns.contains(&path) {
|
||||
return Ok((bus, dbus_conn_path));
|
||||
return Err(MethodErr::no_arg());
|
||||
}
|
||||
|
||||
// It would be nice to have a single main loop, but thread-per-conn is
|
||||
// is easy enough for me to understand in Rust at the moment.
|
||||
let conns_clone = self.conns.clone();
|
||||
let (ok_s, ok_r) = mpsc::channel();
|
||||
std::thread::spawn(move || {
|
||||
let (conn, receiver) = Connection::new(settings, conns_clone).unwrap();
|
||||
conn.run(ok_s, receiver);
|
||||
});
|
||||
|
||||
// Emit a NewConnection signal for the benefit of others, but the caller
|
||||
// learns from our RPC response
|
||||
let sig = telepathy::ConnectionManagerNewConnection {
|
||||
bus_name: bus.to_owned(),
|
||||
object_path: path.clone(),
|
||||
protocol: super::PROTO_NAME.to_string(),
|
||||
};
|
||||
|
||||
// We must wait for the new name to appear on the bus, otherwise the
|
||||
// client can race
|
||||
if let Ok(opt) = ok_r.recv() {
|
||||
if let Some(e) = opt {
|
||||
return Err(e);
|
||||
}
|
||||
|
||||
// Otherwise OK
|
||||
} else {
|
||||
return Err(MethodErr::no_arg());
|
||||
}
|
||||
|
||||
conns.insert(path.clone());
|
||||
|
||||
// FIXME: this thread races with the responses we send. It's possible
|
||||
// the remotes will try to use the new names before they exist on the
|
||||
// bus. This shouldn't be a thread anyway - try to reduce us to a single
|
||||
// thread and a single event loop.
|
||||
std::thread::spawn(move || conn.run());
|
||||
|
||||
// Emit a NewConnection signal for the benefit of others, but the caller
|
||||
// learns immediately
|
||||
let sig = telepathy::ConnectionManagerNewConnection {
|
||||
bus_name: bus.to_owned(),
|
||||
object_path: dbus_conn_path.clone(),
|
||||
protocol: super::PROTO_NAME.to_string(),
|
||||
};
|
||||
|
||||
let dbus_cm_path = dbus::strings::Path::new(CM_OBJECT_PATH.to_string())
|
||||
.expect("Object path should meet DBUS requirements");
|
||||
|
||||
@@ -74,21 +90,21 @@ impl ConnectionManager {
|
||||
.expect("send signal");
|
||||
|
||||
// The bus name *must* be org.freedesktop.Telepathy.Connection.padfoot.delta.<name>
|
||||
Ok((bus, dbus_conn_path))
|
||||
Ok((bus, path))
|
||||
}
|
||||
}
|
||||
|
||||
impl telepathy::ConnectionManager for ConnectionManager {
|
||||
fn get_parameters(&self, protocol: &str) -> Result<Vec<super::ParamSpec>, tree::MethodErr> {
|
||||
fn get_parameters(&self, protocol: &str) -> Result<Vec<super::ParamSpec>, MethodErr> {
|
||||
println!("CM::get_parameters({})", protocol);
|
||||
|
||||
match protocol {
|
||||
super::PROTO_NAME => Ok(super::parameters()),
|
||||
_ => Err(tree::MethodErr::no_arg()), // FIXME: should be NotImplemented?
|
||||
_ => Err(MethodErr::no_arg()), // FIXME: should be NotImplemented?
|
||||
}
|
||||
}
|
||||
|
||||
fn list_protocols(&self) -> Result<Vec<String>, tree::MethodErr> {
|
||||
fn list_protocols(&self) -> Result<Vec<String>, MethodErr> {
|
||||
println!("CM::list_protocols()");
|
||||
|
||||
Ok(vec![super::PROTO_NAME.to_string()])
|
||||
@@ -97,63 +113,58 @@ impl telepathy::ConnectionManager for ConnectionManager {
|
||||
fn request_connection(
|
||||
&self,
|
||||
protocol: &str,
|
||||
params: HashMap<&str, super::Variant>,
|
||||
) -> Result<(String, dbus::Path<'static>), tree::MethodErr> {
|
||||
params: HashMap<&str, VarArg>,
|
||||
) -> Result<(String, dbus::Path<'static>), MethodErr> {
|
||||
println!("CM::request_connection({}, ...)", protocol);
|
||||
|
||||
match protocol {
|
||||
super::PROTO_NAME => self.create_connection(params),
|
||||
_ => Err(tree::MethodErr::no_arg()), // FIXME: should be NotImplemented?
|
||||
super::PROTO_NAME => self.create_connection(super::ConnSettings::from_params(params)?),
|
||||
_ => Err(MethodErr::no_arg()), // FIXME: should be NotImplemented?
|
||||
}
|
||||
}
|
||||
|
||||
fn protocols(
|
||||
&self,
|
||||
) -> Result<HashMap<String, HashMap<String, super::Variant>>, tree::MethodErr> {
|
||||
fn protocols(&self) -> Result<HashMap<String, HashMap<String, VarArg>>, MethodErr> {
|
||||
println!("CM::protocols()");
|
||||
|
||||
// FIXME: so much duplication. It would be good if we could get the
|
||||
// properties from the Protocol instead
|
||||
let mut out = HashMap::<String, HashMap<String, super::Variant>>::new();
|
||||
let mut props = HashMap::<String, super::Variant>::new();
|
||||
let mut out = HashMap::<String, HashMap<String, VarArg>>::new();
|
||||
let mut props = HashMap::<String, VarArg>::new();
|
||||
|
||||
props.insert(
|
||||
"org.freedesktop.Telepathy.Protocol.AuthenticationTypes".to_string(),
|
||||
arg::Variant(Box::new(vec![
|
||||
var_str_vec(vec![
|
||||
"org.freedesktop.Telepathy.Channel.Type.ServerTLSConnection".to_string(),
|
||||
])),
|
||||
]),
|
||||
);
|
||||
|
||||
props.insert(
|
||||
"org.freedesktop.Telepathy.Protocol.ConnectionInterfaces".to_string(),
|
||||
arg::Variant(Box::new(vec![
|
||||
"org.freedesktop.Telepathy.Connection.Interface.Contacts".to_string(),
|
||||
"org.freedesktop.Telepathy.Connection.Interface.Requests".to_string(),
|
||||
])),
|
||||
var_str_vec(super::connection_interfaces()),
|
||||
);
|
||||
props.insert(
|
||||
"org.freedesktop.Telepathy.Protocol.EnglishName".to_string(),
|
||||
arg::Variant(Box::new("Delta Chat".to_string())),
|
||||
var_str("Delta Chat".to_string()),
|
||||
);
|
||||
props.insert(
|
||||
"org.freedesktop.Telepathy.Protocol.Icon".to_string(),
|
||||
arg::Variant(Box::new("im-delta".to_string())),
|
||||
var_str("im-delta".to_string()),
|
||||
);
|
||||
props.insert(
|
||||
"org.freedesktop.Telepathy.Protocol.Interfaces".to_string(),
|
||||
arg::Variant(Box::new(Vec::<String>::new())),
|
||||
var_str_vec(super::protocol_interfaces()),
|
||||
);
|
||||
props.insert(
|
||||
"org.freedesktop.Telepathy.Protocol.Parameters".to_string(),
|
||||
arg::Variant(Box::new(super::parameters())),
|
||||
var_arg(Box::new(super::parameters())),
|
||||
);
|
||||
props.insert(
|
||||
"org.freedesktop.Telepathy.Protocol.RequestableChannelClasses".to_string(),
|
||||
arg::Variant(Box::new(super::requestables())),
|
||||
var_arg(Box::new(super::requestables())),
|
||||
);
|
||||
props.insert(
|
||||
"org.freedesktop.Telepathy.Protocol.VCardField".to_string(),
|
||||
arg::Variant(Box::new("email".to_string())),
|
||||
var_str("email".to_string()),
|
||||
);
|
||||
|
||||
out.insert(super::PROTO_NAME.to_string(), props);
|
||||
@@ -161,7 +172,7 @@ impl telepathy::ConnectionManager for ConnectionManager {
|
||||
Ok(out)
|
||||
}
|
||||
|
||||
fn interfaces(&self) -> Result<Vec<String>, tree::MethodErr> {
|
||||
fn interfaces(&self) -> Result<Vec<String>, MethodErr> {
|
||||
println!("CM::interfaces()");
|
||||
|
||||
Ok(vec![])
|
||||
|
138
src/padfoot/message.rs
Normal file
138
src/padfoot/message.rs
Normal file
@@ -0,0 +1,138 @@
|
||||
use crate::padfoot::{var_bytearray, var_i64, var_str, var_u32, VarArg};
|
||||
|
||||
use async_std::path::{Path, PathBuf};
|
||||
use dc::constants::Viewtype as Vt;
|
||||
use dc::message::Message;
|
||||
use deltachat as dc;
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
type Part = HashMap<String, VarArg>;
|
||||
type Result<T> = std::result::Result<T, Box<dyn std::error::Error>>;
|
||||
|
||||
// Turns a deltachat::message::Message into a Telepathy Message_Part_List
|
||||
pub fn convert_msg(blobdir: &Path, msg: &Message) -> Result<Vec<Part>> {
|
||||
if msg.is_setupmessage() {
|
||||
return Ok(convert_setupmessage(msg));
|
||||
}
|
||||
|
||||
let mut parts = vec![make_props(msg)];
|
||||
|
||||
// TODO: Check. Can a deltachat message have multiple parts? Can an image
|
||||
// viewtype have text as well?
|
||||
let result = match msg.get_viewtype() {
|
||||
Vt::Text => build_txt(msg),
|
||||
Vt::Unknown => build_unknown(msg),
|
||||
Vt::Audio | Vt::Voice => build_snd(msg),
|
||||
Vt::Video => build_vid(msg),
|
||||
_ => build_attachment(blobdir, msg),
|
||||
};
|
||||
|
||||
result.map(|mut more| {
|
||||
parts.append(&mut more);
|
||||
parts
|
||||
})
|
||||
}
|
||||
|
||||
fn convert_setupmessage(msg: &Message) -> Vec<Part> {
|
||||
let msg_id = msg.get_id();
|
||||
vec![
|
||||
make_props(msg),
|
||||
make_plain(&format!("Setup message received. To apply it, reply with:\nIMEX: {} nnnn-nnnn-nnnn-nnnn-nnnn-nnnn-nnnn-nnnn-nnnn\nNo whitespace in the setup-code!", msg_id.to_u32())),
|
||||
]
|
||||
}
|
||||
|
||||
fn make_props(msg: &Message) -> Part {
|
||||
let msg_id = msg.get_id();
|
||||
let mut out = HashMap::new();
|
||||
|
||||
out.insert(
|
||||
"message-token".to_string(),
|
||||
var_str(format!("{}", msg_id.to_u32())),
|
||||
);
|
||||
out.insert("message-sent".to_string(), var_i64(msg.get_timestamp()));
|
||||
out.insert(
|
||||
"message-received".to_string(),
|
||||
var_i64(msg.get_received_timestamp()),
|
||||
);
|
||||
out.insert("message-sender".to_string(), var_u32(msg.get_from_id()));
|
||||
// props.insert("message-sender-id", var_str()); // This doesn't need to be sent
|
||||
// props.insert("sender-nickname", var_str()); // Can we get away without this one?
|
||||
out.insert("message-type".to_string(), var_u32(0)); // normal
|
||||
|
||||
// These relate to superseded messages
|
||||
// props.insert("supersedes", var_str());
|
||||
// props.insert("original-message-sent", var_i64());
|
||||
// props.insert("original-message-received", var_i64());
|
||||
|
||||
out.insert("pending-message-id".to_string(), var_u32(msg_id.to_u32()));
|
||||
|
||||
// Don't need these
|
||||
// props.insert("interface", var_str());
|
||||
// props.insert("scrollback", var_vool());
|
||||
// props.insert("silent", var_bool());
|
||||
// props.insert("rescued", var_bool());
|
||||
|
||||
out
|
||||
}
|
||||
|
||||
fn make_plain(text: &str) -> Part {
|
||||
make_content("text/plain", None, var_str(text.to_string()))
|
||||
}
|
||||
|
||||
fn make_content(type_: &str, id: Option<&str>, content: VarArg) -> Part {
|
||||
let mut out = HashMap::new();
|
||||
|
||||
out.insert("content-type".to_string(), var_str(type_.to_string()));
|
||||
out.insert("content".to_string(), content);
|
||||
|
||||
id.map(|txt| out.insert("identifier".to_string(), var_str(txt.to_string())));
|
||||
|
||||
out
|
||||
}
|
||||
|
||||
fn build_snd(_msg: &Message) -> Result<Vec<Part>> {
|
||||
Ok(vec![make_plain("(a sound file was received)")])
|
||||
}
|
||||
|
||||
fn build_txt(msg: &Message) -> Result<Vec<Part>> {
|
||||
Ok(vec![make_plain(
|
||||
&msg.get_text().unwrap_or_else(|| "".to_string()),
|
||||
)])
|
||||
}
|
||||
|
||||
fn build_unknown(_msg: &Message) -> Result<Vec<Part>> {
|
||||
Ok(vec![make_plain("(a message of unknown type was received)")])
|
||||
}
|
||||
|
||||
fn build_vid(_msg: &Message) -> Result<Vec<Part>> {
|
||||
Ok(vec![make_plain("(a video was received)")])
|
||||
}
|
||||
|
||||
// The message contains a file. Detect the content-type and construct a part
|
||||
// containing the data in full.
|
||||
fn build_attachment(blobdir: &Path, msg: &Message) -> Result<Vec<Part>> {
|
||||
let mime = msg
|
||||
.get_filemime()
|
||||
.unwrap_or_else(|| "application/octet-stream".to_string());
|
||||
let filename = msg.get_filename().ok_or("Failed to get filename")?;
|
||||
|
||||
let path: PathBuf = [blobdir, &Path::new(&filename)].iter().collect();
|
||||
|
||||
let data =
|
||||
std::fs::read(&path).map_err(|e| format!("Failed to read file {:?}: {}", path, e))?;
|
||||
|
||||
println!("MIME type for attachment: {}", mime);
|
||||
|
||||
let html = make_content(
|
||||
"text/html",
|
||||
None,
|
||||
var_str("<img src=\"cid:picture\" />".to_string()),
|
||||
);
|
||||
|
||||
let txt = make_plain("(an image was sent but cannot be displayed)");
|
||||
|
||||
let blob = make_content(&mime, Some("picture"), var_bytearray(data));
|
||||
|
||||
Ok(vec![html, txt, blob])
|
||||
}
|
@@ -1,30 +1,30 @@
|
||||
use crate::padfoot::{var_bool, var_str, var_u32, VarArg};
|
||||
use crate::telepathy;
|
||||
use dbus::{arg, tree};
|
||||
|
||||
use dbus::tree::MethodErr;
|
||||
use deltachat as dc;
|
||||
use std::collections::HashMap;
|
||||
|
||||
pub const PROTO_OBJECT_PATH: &'static str =
|
||||
"/org/freedesktop/Telepathy/ConnectionManager/padfoot/delta";
|
||||
pub const PROTO_BUS_NAME: &'static str =
|
||||
"org.freedesktop.Telepathy.ConnectionManager.padfoot.delta";
|
||||
use super::ConnSettings;
|
||||
|
||||
pub const PROTO_NAME: &'static str = "delta";
|
||||
pub const PROTO_OBJECT_PATH: &str = "/org/freedesktop/Telepathy/ConnectionManager/padfoot/delta";
|
||||
pub const PROTO_BUS_NAME: &str = "org.freedesktop.Telepathy.ConnectionManager.padfoot.delta";
|
||||
pub const PROTO_NAME: &str = "delta";
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Protocol {}
|
||||
|
||||
pub type Variant = arg::Variant<Box<dyn arg::RefArg + 'static>>;
|
||||
|
||||
pub type ParamSpec = (
|
||||
String, // Name
|
||||
u32, // Flags (Conn_Mgr_Param_Flags)
|
||||
String, // Signature
|
||||
Variant, // Default
|
||||
String, // Name
|
||||
u32, // Flags (Conn_Mgr_Param_Flags)
|
||||
String, // Signature
|
||||
VarArg, // Default value
|
||||
);
|
||||
|
||||
// Requestable_Channel_Class
|
||||
pub type RequestableChannelSpec = (
|
||||
HashMap<String, Variant>, // Fixed properties
|
||||
Vec<String>, // Allowed properties
|
||||
HashMap<String, VarArg>, // Fixed properties
|
||||
Vec<String>, // Allowed properties
|
||||
);
|
||||
|
||||
// FIXME: these should come from codegen
|
||||
@@ -41,30 +41,36 @@ pub fn parameters() -> Vec<ParamSpec> {
|
||||
"account".to_string(),
|
||||
FLAG_REQUIRED,
|
||||
"s".to_string(),
|
||||
arg::Variant(Box::new("".to_string())),
|
||||
var_str("".to_string()),
|
||||
),
|
||||
(
|
||||
"password".to_string(),
|
||||
FLAG_REQUIRED | FLAG_SECRET,
|
||||
"s".to_string(),
|
||||
arg::Variant(Box::new("".to_string())),
|
||||
var_str("".to_string()),
|
||||
),
|
||||
("bcc-self".to_string(), 0, "b".to_string(), var_bool(false)),
|
||||
]
|
||||
}
|
||||
|
||||
pub fn protocol_interfaces() -> Vec<String> {
|
||||
vec![
|
||||
"org.freedesktop.Telepathy.Protocol".to_string(),
|
||||
"org.freedesktop.Telepathy.Protocol.Interface.Presence".to_string(),
|
||||
]
|
||||
}
|
||||
|
||||
pub fn requestables() -> Vec<RequestableChannelSpec> {
|
||||
let mut rf = HashMap::<String, Variant>::new();
|
||||
let mut rf = HashMap::<String, VarArg>::new();
|
||||
|
||||
rf.insert(
|
||||
"org.freedesktop.Telepathy.Channel.ChannelType".to_string(),
|
||||
arg::Variant(Box::new(
|
||||
"org.freedesktop.Telepathy.Channel.Type.Text".to_string(),
|
||||
)),
|
||||
var_str("org.freedesktop.Telepathy.Channel.Type.Text".to_string()),
|
||||
);
|
||||
|
||||
rf.insert(
|
||||
"org.freedesktop.Telepathy.Channel.TargetHandleType".to_string(),
|
||||
arg::Variant(Box::new(1)),
|
||||
var_u32(1),
|
||||
);
|
||||
|
||||
let ra = vec![
|
||||
@@ -82,62 +88,63 @@ impl AsRef<dyn telepathy::Protocol + 'static> for std::rc::Rc<Protocol> {
|
||||
}
|
||||
|
||||
impl telepathy::Protocol for Protocol {
|
||||
fn identify_account(&self, params: HashMap<&str, Variant>) -> Result<String, tree::MethodErr> {
|
||||
println!("Protocol::identify_account({:?})", params);
|
||||
fn identify_account(&self, params: HashMap<&str, VarArg>) -> Result<String, MethodErr> {
|
||||
println!("Protocol::identify_account(...)");
|
||||
|
||||
Err(tree::MethodErr::no_arg())
|
||||
let settings = ConnSettings::from_params(params)?;
|
||||
|
||||
Ok(settings.id())
|
||||
}
|
||||
|
||||
fn normalize_contact(&self, contact_id: &str) -> Result<String, tree::MethodErr> {
|
||||
fn normalize_contact(&self, contact_id: &str) -> Result<String, MethodErr> {
|
||||
println!("Protocol::normalize_contact({})", contact_id);
|
||||
|
||||
Err(tree::MethodErr::no_arg())
|
||||
Ok(dc::contact::addr_normalize(contact_id).to_string())
|
||||
}
|
||||
|
||||
fn interfaces(&self) -> Result<Vec<String>, tree::MethodErr> {
|
||||
fn interfaces(&self) -> Result<Vec<String>, MethodErr> {
|
||||
println!("Protocol::interfaces()");
|
||||
|
||||
Ok(vec![])
|
||||
Ok(protocol_interfaces())
|
||||
}
|
||||
fn parameters(&self) -> Result<Vec<ParamSpec>, tree::MethodErr> {
|
||||
|
||||
fn parameters(&self) -> Result<Vec<ParamSpec>, MethodErr> {
|
||||
println!("Protocol::parameters()");
|
||||
|
||||
Ok(parameters())
|
||||
}
|
||||
|
||||
fn connection_interfaces(&self) -> Result<Vec<String>, tree::MethodErr> {
|
||||
fn connection_interfaces(&self) -> Result<Vec<String>, MethodErr> {
|
||||
println!("Protocol::connection_interfaces()");
|
||||
|
||||
Ok(vec![
|
||||
"org.freedesktop.Telepathy.Connection.Interface.Contacts".to_string(),
|
||||
"org.freedesktop.Telepathy.Connection.Interface.Requests".to_string(),
|
||||
])
|
||||
Ok(super::connection_interfaces())
|
||||
}
|
||||
fn requestable_channel_classes(&self) -> Result<Vec<RequestableChannelSpec>, tree::MethodErr> {
|
||||
|
||||
fn requestable_channel_classes(&self) -> Result<Vec<RequestableChannelSpec>, MethodErr> {
|
||||
println!("Protocol::requestable_channel_classes()");
|
||||
|
||||
Ok(requestables())
|
||||
}
|
||||
|
||||
fn vcard_field(&self) -> Result<String, tree::MethodErr> {
|
||||
fn vcard_field(&self) -> Result<String, MethodErr> {
|
||||
println!("Protocol::vcard_field()");
|
||||
|
||||
Ok("email".to_string())
|
||||
}
|
||||
|
||||
fn english_name(&self) -> Result<String, tree::MethodErr> {
|
||||
fn english_name(&self) -> Result<String, MethodErr> {
|
||||
println!("Protocol::english_name()");
|
||||
|
||||
Ok("Delta Chat".to_string())
|
||||
}
|
||||
|
||||
fn icon(&self) -> Result<String, tree::MethodErr> {
|
||||
fn icon(&self) -> Result<String, MethodErr> {
|
||||
println!("Protocol::icon()");
|
||||
|
||||
Ok("im-delta".to_string())
|
||||
}
|
||||
|
||||
fn authentication_types(&self) -> Result<Vec<String>, tree::MethodErr> {
|
||||
fn authentication_types(&self) -> Result<Vec<String>, MethodErr> {
|
||||
println!("Protocol::authentication_types()");
|
||||
|
||||
Ok(vec![
|
||||
@@ -145,3 +152,32 @@ impl telepathy::Protocol for Protocol {
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
pub type SimpleStatusSpec = (
|
||||
u32, // connection presence type
|
||||
bool, // may set on self?
|
||||
bool, // can have message?
|
||||
);
|
||||
|
||||
pub fn statuses() -> HashMap<String, SimpleStatusSpec> {
|
||||
let mut out = HashMap::<String, SimpleStatusSpec>::new();
|
||||
|
||||
out.insert("available".to_string(), (2, true, false));
|
||||
out.insert("offline".to_string(), (1, true, false));
|
||||
|
||||
out
|
||||
}
|
||||
|
||||
impl AsRef<dyn telepathy::ProtocolInterfacePresence + 'static> for std::rc::Rc<Protocol> {
|
||||
fn as_ref(&self) -> &(dyn telepathy::ProtocolInterfacePresence + 'static) {
|
||||
&**self
|
||||
}
|
||||
}
|
||||
|
||||
impl telepathy::ProtocolInterfacePresence for Protocol {
|
||||
fn statuses(&self) -> Result<HashMap<String, SimpleStatusSpec>, MethodErr> {
|
||||
println!("Protocol::presences()");
|
||||
|
||||
Ok(statuses())
|
||||
}
|
||||
}
|
||||
|
41
src/padfoot/var_arg.rs
Normal file
41
src/padfoot/var_arg.rs
Normal file
@@ -0,0 +1,41 @@
|
||||
use dbus::arg::{RefArg, Variant};
|
||||
|
||||
use std::convert::TryInto;
|
||||
|
||||
pub type VarArg = Variant<Box<dyn RefArg + 'static>>;
|
||||
|
||||
pub fn var_arg(item: Box<dyn RefArg + 'static>) -> VarArg {
|
||||
Variant(item)
|
||||
}
|
||||
|
||||
pub fn var_str(item: String) -> VarArg {
|
||||
var_arg(Box::new(item))
|
||||
}
|
||||
|
||||
pub fn var_str_vec(item: Vec<String>) -> VarArg {
|
||||
var_arg(Box::new(item))
|
||||
}
|
||||
|
||||
pub fn var_bool(item: bool) -> VarArg {
|
||||
var_arg(Box::new(item))
|
||||
}
|
||||
|
||||
pub fn var_bytearray(item: std::vec::Vec<u8>) -> VarArg {
|
||||
var_arg(Box::new(item))
|
||||
}
|
||||
|
||||
pub fn var_u32(item: u32) -> VarArg {
|
||||
var_arg(Box::new(item))
|
||||
}
|
||||
|
||||
pub fn var_i64(item: i64) -> VarArg {
|
||||
var_arg(Box::new(item))
|
||||
}
|
||||
|
||||
pub fn get_var_str(from: &VarArg) -> String {
|
||||
from.0.as_str().unwrap().to_string()
|
||||
}
|
||||
|
||||
pub fn get_var_u32(from: &VarArg) -> u32 {
|
||||
from.0.as_u64().unwrap().try_into().unwrap()
|
||||
}
|
122
src/telepathy.rs
122
src/telepathy.rs
@@ -1,365 +1,485 @@
|
||||
#![allow(unused)]
|
||||
#![allow(clippy::all)]
|
||||
|
||||
#[allow(clippy::all)]
|
||||
mod account_interface_addressing;
|
||||
pub use self::account_interface_addressing::*;
|
||||
|
||||
#[allow(clippy::all)]
|
||||
mod account_interface_avatar;
|
||||
pub use self::account_interface_avatar::*;
|
||||
|
||||
#[allow(clippy::all)]
|
||||
mod account_interface_external_password_storage;
|
||||
pub use self::account_interface_external_password_storage::*;
|
||||
|
||||
#[allow(clippy::all)]
|
||||
mod account_interface_hidden;
|
||||
pub use self::account_interface_hidden::*;
|
||||
|
||||
#[allow(clippy::all)]
|
||||
mod account_interface_storage;
|
||||
pub use self::account_interface_storage::*;
|
||||
|
||||
#[allow(clippy::all)]
|
||||
mod account_manager_interface_hidden;
|
||||
pub use self::account_manager_interface_hidden::*;
|
||||
|
||||
#[allow(clippy::all)]
|
||||
mod account_manager;
|
||||
pub use self::account_manager::*;
|
||||
|
||||
#[allow(clippy::all)]
|
||||
mod account;
|
||||
pub use self::account::*;
|
||||
|
||||
#[allow(clippy::all)]
|
||||
mod all;
|
||||
pub use self::all::*;
|
||||
|
||||
#[allow(clippy::all)]
|
||||
mod authentication_tls_certificate;
|
||||
pub use self::authentication_tls_certificate::*;
|
||||
|
||||
#[allow(clippy::all)]
|
||||
mod call_content_interface_audio_control;
|
||||
pub use self::call_content_interface_audio_control::*;
|
||||
|
||||
#[allow(clippy::all)]
|
||||
mod call_content_interface_dtmf;
|
||||
pub use self::call_content_interface_dtmf::*;
|
||||
|
||||
#[allow(clippy::all)]
|
||||
mod call_content_interface_media;
|
||||
pub use self::call_content_interface_media::*;
|
||||
|
||||
#[allow(clippy::all)]
|
||||
mod call_content_interface_video_control;
|
||||
pub use self::call_content_interface_video_control::*;
|
||||
|
||||
#[allow(clippy::all)]
|
||||
mod call_content_media_description_interface_rtcp_extended_reports;
|
||||
pub use self::call_content_media_description_interface_rtcp_extended_reports::*;
|
||||
|
||||
#[allow(clippy::all)]
|
||||
mod call_content_media_description_interface_rtcp_feedback;
|
||||
pub use self::call_content_media_description_interface_rtcp_feedback::*;
|
||||
|
||||
#[allow(clippy::all)]
|
||||
mod call_content_media_description_interface_rtp_header_extensions;
|
||||
pub use self::call_content_media_description_interface_rtp_header_extensions::*;
|
||||
|
||||
#[allow(clippy::all)]
|
||||
mod call_content_media_description;
|
||||
pub use self::call_content_media_description::*;
|
||||
|
||||
#[allow(clippy::all)]
|
||||
mod call_content;
|
||||
pub use self::call_content::*;
|
||||
|
||||
#[allow(clippy::all)]
|
||||
mod call_interface_mute;
|
||||
pub use self::call_interface_mute::*;
|
||||
|
||||
#[allow(clippy::all)]
|
||||
mod call_stream_endpoint;
|
||||
pub use self::call_stream_endpoint::*;
|
||||
|
||||
#[allow(clippy::all)]
|
||||
mod call_stream_interface_media;
|
||||
pub use self::call_stream_interface_media::*;
|
||||
|
||||
#[allow(clippy::all)]
|
||||
mod call_stream;
|
||||
pub use self::call_stream::*;
|
||||
|
||||
#[allow(clippy::all)]
|
||||
mod channel_bundle;
|
||||
pub use self::channel_bundle::*;
|
||||
|
||||
#[allow(clippy::all)]
|
||||
mod channel_dispatcher_interface_messages1;
|
||||
pub use self::channel_dispatcher_interface_messages1::*;
|
||||
|
||||
#[allow(clippy::all)]
|
||||
mod channel_dispatcher_interface_operation_list;
|
||||
pub use self::channel_dispatcher_interface_operation_list::*;
|
||||
|
||||
#[allow(clippy::all)]
|
||||
mod channel_dispatcher;
|
||||
pub use self::channel_dispatcher::*;
|
||||
|
||||
#[allow(clippy::all)]
|
||||
mod channel_dispatch_operation;
|
||||
pub use self::channel_dispatch_operation::*;
|
||||
|
||||
#[allow(clippy::all)]
|
||||
mod channel_future;
|
||||
pub use self::channel_future::*;
|
||||
|
||||
#[allow(clippy::all)]
|
||||
mod channel_handler;
|
||||
pub use self::channel_handler::*;
|
||||
|
||||
#[allow(clippy::all)]
|
||||
mod channel_interface_addressing;
|
||||
pub use self::channel_interface_addressing::*;
|
||||
|
||||
#[allow(clippy::all)]
|
||||
mod channel_interface_anonymity;
|
||||
pub use self::channel_interface_anonymity::*;
|
||||
|
||||
#[allow(clippy::all)]
|
||||
mod channel_interface_call_state;
|
||||
pub use self::channel_interface_call_state::*;
|
||||
|
||||
#[allow(clippy::all)]
|
||||
mod channel_interface_captcha_authentication;
|
||||
pub use self::channel_interface_captcha_authentication::*;
|
||||
|
||||
#[allow(clippy::all)]
|
||||
mod channel_interface_chat_state;
|
||||
pub use self::channel_interface_chat_state::*;
|
||||
|
||||
#[allow(clippy::all)]
|
||||
mod channel_interface_conference;
|
||||
pub use self::channel_interface_conference::*;
|
||||
|
||||
#[allow(clippy::all)]
|
||||
mod channel_interface_credentials_storage;
|
||||
pub use self::channel_interface_credentials_storage::*;
|
||||
|
||||
#[allow(clippy::all)]
|
||||
mod channel_interface_destroyable;
|
||||
pub use self::channel_interface_destroyable::*;
|
||||
|
||||
#[allow(clippy::all)]
|
||||
mod channel_interface_dtmf;
|
||||
pub use self::channel_interface_dtmf::*;
|
||||
|
||||
#[allow(clippy::all)]
|
||||
mod channel_interface_file_transfer_metadata;
|
||||
pub use self::channel_interface_file_transfer_metadata::*;
|
||||
|
||||
#[allow(clippy::all)]
|
||||
mod channel_interface_group;
|
||||
pub use self::channel_interface_group::*;
|
||||
|
||||
#[allow(clippy::all)]
|
||||
mod channel_interface_hold;
|
||||
pub use self::channel_interface_hold::*;
|
||||
|
||||
#[allow(clippy::all)]
|
||||
mod channel_interface_html;
|
||||
pub use self::channel_interface_html::*;
|
||||
|
||||
#[allow(clippy::all)]
|
||||
mod channel_interface_media_signalling;
|
||||
pub use self::channel_interface_media_signalling::*;
|
||||
|
||||
#[allow(clippy::all)]
|
||||
mod channel_interface_mergeable_conference;
|
||||
pub use self::channel_interface_mergeable_conference::*;
|
||||
|
||||
#[allow(clippy::all)]
|
||||
mod channel_interface_messages;
|
||||
pub use self::channel_interface_messages::*;
|
||||
|
||||
#[allow(clippy::all)]
|
||||
mod channel_interface_password;
|
||||
pub use self::channel_interface_password::*;
|
||||
|
||||
#[allow(clippy::all)]
|
||||
mod channel_interface_picture;
|
||||
pub use self::channel_interface_picture::*;
|
||||
|
||||
#[allow(clippy::all)]
|
||||
mod channel_interface_room_config;
|
||||
pub use self::channel_interface_room_config::*;
|
||||
|
||||
#[allow(clippy::all)]
|
||||
mod channel_interface_room;
|
||||
pub use self::channel_interface_room::*;
|
||||
|
||||
#[allow(clippy::all)]
|
||||
mod channel_interface_sasl_authentication;
|
||||
pub use self::channel_interface_sasl_authentication::*;
|
||||
|
||||
#[allow(clippy::all)]
|
||||
mod channel_interface_securable;
|
||||
pub use self::channel_interface_securable::*;
|
||||
|
||||
#[allow(clippy::all)]
|
||||
mod channel_interface_service_point;
|
||||
pub use self::channel_interface_service_point::*;
|
||||
|
||||
#[allow(clippy::all)]
|
||||
mod channel_interface_sms;
|
||||
pub use self::channel_interface_sms::*;
|
||||
|
||||
#[allow(clippy::all)]
|
||||
mod channel_interface_splittable;
|
||||
pub use self::channel_interface_splittable::*;
|
||||
|
||||
#[allow(clippy::all)]
|
||||
mod channel_interface_subject;
|
||||
pub use self::channel_interface_subject::*;
|
||||
|
||||
#[allow(clippy::all)]
|
||||
mod channel_interface_transfer;
|
||||
pub use self::channel_interface_transfer::*;
|
||||
|
||||
#[allow(clippy::all)]
|
||||
mod channel_interface_tube;
|
||||
pub use self::channel_interface_tube::*;
|
||||
|
||||
#[allow(clippy::all)]
|
||||
mod channel_request;
|
||||
pub use self::channel_request::*;
|
||||
|
||||
#[allow(clippy::all)]
|
||||
mod channel_type_call;
|
||||
pub use self::channel_type_call::*;
|
||||
|
||||
#[allow(clippy::all)]
|
||||
mod channel_type_contact_list;
|
||||
pub use self::channel_type_contact_list::*;
|
||||
|
||||
#[allow(clippy::all)]
|
||||
mod channel_type_contact_search;
|
||||
pub use self::channel_type_contact_search::*;
|
||||
|
||||
#[allow(clippy::all)]
|
||||
mod channel_type_dbus_tube;
|
||||
pub use self::channel_type_dbus_tube::*;
|
||||
|
||||
#[allow(clippy::all)]
|
||||
mod channel_type_file_transfer;
|
||||
pub use self::channel_type_file_transfer::*;
|
||||
|
||||
#[allow(clippy::all)]
|
||||
mod channel_type_room_list;
|
||||
pub use self::channel_type_room_list::*;
|
||||
|
||||
#[allow(clippy::all)]
|
||||
mod channel_type_server_authentication;
|
||||
pub use self::channel_type_server_authentication::*;
|
||||
|
||||
#[allow(clippy::all)]
|
||||
mod channel_type_server_tls_connection;
|
||||
pub use self::channel_type_server_tls_connection::*;
|
||||
|
||||
#[allow(clippy::all)]
|
||||
mod channel_type_streamed_media;
|
||||
pub use self::channel_type_streamed_media::*;
|
||||
|
||||
#[allow(clippy::all)]
|
||||
mod channel_type_stream_tube;
|
||||
pub use self::channel_type_stream_tube::*;
|
||||
|
||||
#[allow(clippy::all)]
|
||||
mod channel_type_text;
|
||||
pub use self::channel_type_text::*;
|
||||
|
||||
#[allow(clippy::all)]
|
||||
mod channel_type_tubes;
|
||||
pub use self::channel_type_tubes::*;
|
||||
|
||||
#[allow(clippy::all)]
|
||||
mod channel;
|
||||
pub use self::channel::*;
|
||||
|
||||
#[allow(clippy::all)]
|
||||
mod client_approver;
|
||||
pub use self::client_approver::*;
|
||||
|
||||
#[allow(clippy::all)]
|
||||
mod client_handler_future;
|
||||
pub use self::client_handler_future::*;
|
||||
|
||||
#[allow(clippy::all)]
|
||||
mod client_handler;
|
||||
pub use self::client_handler::*;
|
||||
|
||||
#[allow(clippy::all)]
|
||||
mod client_interface_requests;
|
||||
pub use self::client_interface_requests::*;
|
||||
|
||||
#[allow(clippy::all)]
|
||||
mod client_observer;
|
||||
pub use self::client_observer::*;
|
||||
|
||||
#[allow(clippy::all)]
|
||||
mod client;
|
||||
pub use self::client::*;
|
||||
|
||||
#[allow(clippy::all)]
|
||||
mod connection_interface_addressing;
|
||||
pub use self::connection_interface_addressing::*;
|
||||
|
||||
#[allow(clippy::all)]
|
||||
mod connection_interface_aliasing;
|
||||
pub use self::connection_interface_aliasing::*;
|
||||
|
||||
#[allow(clippy::all)]
|
||||
mod connection_interface_anonymity;
|
||||
pub use self::connection_interface_anonymity::*;
|
||||
|
||||
#[allow(clippy::all)]
|
||||
mod connection_interface_avatars;
|
||||
pub use self::connection_interface_avatars::*;
|
||||
|
||||
#[allow(clippy::all)]
|
||||
mod connection_interface_balance;
|
||||
pub use self::connection_interface_balance::*;
|
||||
|
||||
#[allow(clippy::all)]
|
||||
mod connection_interface_capabilities;
|
||||
pub use self::connection_interface_capabilities::*;
|
||||
|
||||
#[allow(clippy::all)]
|
||||
mod connection_interface_cellular;
|
||||
pub use self::connection_interface_cellular::*;
|
||||
|
||||
#[allow(clippy::all)]
|
||||
mod connection_interface_client_types;
|
||||
pub use self::connection_interface_client_types::*;
|
||||
|
||||
#[allow(clippy::all)]
|
||||
mod connection_interface_communication_policy;
|
||||
pub use self::connection_interface_communication_policy::*;
|
||||
|
||||
#[allow(clippy::all)]
|
||||
mod connection_interface_contact_blocking;
|
||||
pub use self::connection_interface_contact_blocking::*;
|
||||
|
||||
#[allow(clippy::all)]
|
||||
mod connection_interface_contact_capabilities;
|
||||
pub use self::connection_interface_contact_capabilities::*;
|
||||
|
||||
#[allow(clippy::all)]
|
||||
mod connection_interface_contact_groups;
|
||||
pub use self::connection_interface_contact_groups::*;
|
||||
|
||||
#[allow(clippy::all)]
|
||||
mod connection_interface_contact_info;
|
||||
pub use self::connection_interface_contact_info::*;
|
||||
|
||||
#[allow(clippy::all)]
|
||||
mod connection_interface_contact_list;
|
||||
pub use self::connection_interface_contact_list::*;
|
||||
|
||||
#[allow(clippy::all)]
|
||||
mod connection_interface_contacts;
|
||||
pub use self::connection_interface_contacts::*;
|
||||
|
||||
#[allow(clippy::all)]
|
||||
mod connection_interface_forwarding;
|
||||
pub use self::connection_interface_forwarding::*;
|
||||
|
||||
#[allow(clippy::all)]
|
||||
mod connection_interface_irc_command1;
|
||||
pub use self::connection_interface_irc_command1::*;
|
||||
|
||||
#[allow(clippy::all)]
|
||||
mod connection_interface_keepalive;
|
||||
pub use self::connection_interface_keepalive::*;
|
||||
|
||||
#[allow(clippy::all)]
|
||||
mod connection_interface_location;
|
||||
pub use self::connection_interface_location::*;
|
||||
|
||||
#[allow(clippy::all)]
|
||||
mod connection_interface_mail_notification;
|
||||
pub use self::connection_interface_mail_notification::*;
|
||||
|
||||
#[allow(clippy::all)]
|
||||
mod connection_interface_power_saving;
|
||||
pub use self::connection_interface_power_saving::*;
|
||||
|
||||
#[allow(clippy::all)]
|
||||
mod connection_interface_presence;
|
||||
pub use self::connection_interface_presence::*;
|
||||
|
||||
#[allow(clippy::all)]
|
||||
mod connection_interface_privacy;
|
||||
pub use self::connection_interface_privacy::*;
|
||||
|
||||
#[allow(clippy::all)]
|
||||
mod connection_interface_renaming;
|
||||
pub use self::connection_interface_renaming::*;
|
||||
|
||||
#[allow(clippy::all)]
|
||||
mod connection_interface_requests;
|
||||
pub use self::connection_interface_requests::*;
|
||||
|
||||
#[allow(clippy::all)]
|
||||
mod connection_interface_resources;
|
||||
pub use self::connection_interface_resources::*;
|
||||
|
||||
#[allow(clippy::all)]
|
||||
mod connection_interface_service_point;
|
||||
pub use self::connection_interface_service_point::*;
|
||||
|
||||
#[allow(clippy::all)]
|
||||
mod connection_interface_sidecars1;
|
||||
pub use self::connection_interface_sidecars1::*;
|
||||
|
||||
#[allow(clippy::all)]
|
||||
mod connection_interface_simple_presence;
|
||||
pub use self::connection_interface_simple_presence::*;
|
||||
|
||||
#[allow(clippy::all)]
|
||||
mod connection_manager_interface_account_storage;
|
||||
pub use self::connection_manager_interface_account_storage::*;
|
||||
|
||||
#[allow(clippy::all)]
|
||||
mod connection_manager;
|
||||
pub use self::connection_manager::*;
|
||||
|
||||
#[allow(clippy::all)]
|
||||
mod connection;
|
||||
pub use self::connection::*;
|
||||
|
||||
#[allow(clippy::all)]
|
||||
mod debug;
|
||||
pub use self::debug::*;
|
||||
|
||||
#[allow(clippy::all)]
|
||||
mod errors;
|
||||
pub use self::errors::*;
|
||||
|
||||
#[allow(clippy::all)]
|
||||
mod generic_types;
|
||||
pub use self::generic_types::*;
|
||||
|
||||
#[allow(clippy::all)]
|
||||
mod media_session_handler;
|
||||
pub use self::media_session_handler::*;
|
||||
|
||||
#[allow(clippy::all)]
|
||||
mod media_stream_handler;
|
||||
pub use self::media_stream_handler::*;
|
||||
|
||||
#[allow(clippy::all)]
|
||||
mod properties_interface;
|
||||
pub use self::properties_interface::*;
|
||||
|
||||
#[allow(clippy::all)]
|
||||
mod protocol_interface_addressing;
|
||||
pub use self::protocol_interface_addressing::*;
|
||||
|
||||
#[allow(clippy::all)]
|
||||
mod protocol_interface_avatars;
|
||||
pub use self::protocol_interface_avatars::*;
|
||||
|
||||
#[allow(clippy::all)]
|
||||
mod protocol_interface_presence;
|
||||
pub use self::protocol_interface_presence::*;
|
||||
|
||||
#[allow(clippy::all)]
|
||||
mod protocol;
|
||||
pub use self::protocol::*;
|
||||
|
||||
#[allow(clippy::all)]
|
||||
mod template;
|
||||
pub use self::template::*;
|
||||
|
8
telepathy-padfoot.service
Normal file
8
telepathy-padfoot.service
Normal file
@@ -0,0 +1,8 @@
|
||||
[Unit]
|
||||
Description=Telepathy Delta Chat service
|
||||
Documentation=man:telepathy-padfoot(8)
|
||||
|
||||
[Service]
|
||||
Type=dbus
|
||||
BusName=org.freedesktop.Telepathy.ConnectionManager.padfoot
|
||||
ExecStart=/usr/lib/telepathy/telepathy-padfoot
|
Reference in New Issue
Block a user