Compare commits

...

32 Commits

Author SHA1 Message Date
7707242d10 Merge branch 'async' 2020-05-28 09:38:48 +01:00
bd76603c54 Upgrade to Delta v1.33.0
Despite the incremental version number, this is a backward-incompatible
change, switching code to async. It does allow us to reduce the number
of threads significantly, though.
2020-05-28 09:37:50 +01:00
9e764d72a1 Add image receipt to the text channel
This is disturbingly useless with Empathy because it doesn't know how
to display text/html parts, or plain image/png parts either :/

Time to start playing with KDE Spacebar? How does it handle them?
2020-05-27 01:19:43 +01:00
b9faad742b Partial implementation of contact removal
Empathy needs you to cycle the connection before it notices.
2020-05-24 00:24:15 +01:00
370f5076a1 Add and set the "Bcc self" option. 2020-05-23 23:52:42 +01:00
373311e826 HAXXX: A crude form of setup message acceptance 2020-05-23 22:49:15 +01:00
67a8715a25 Add an (untested) service file 2020-05-23 17:57:37 +01:00
c773146b26 It's called Mobian, not Moblin 2020-05-23 17:51:39 +01:00
7b1b8bdc83 cargo-about output 2020-05-23 17:48:13 +01:00
d06badfc96 *sobbing subsides* 2020-05-23 16:05:05 +01:00
8dbd023718 More sobbing 2020-05-23 14:48:35 +01:00
7cee6348fd Cry in public 2020-05-23 14:35:26 +01:00
14aa639a4b Fight with mobile linux for a bit 2020-05-22 10:29:10 +01:00
efe97a33c4 Fix messages on self-initiated channels 2020-05-21 11:06:07 +01:00
d77d04e9b1 Fix messages on remote-initiated chats 2020-05-21 10:49:33 +01:00
db7ecc6d98 Add the ContactList interface to the manager file 2020-05-21 10:49:15 +01:00
15174ea03f Add a very broken ContactList + outgoing channel implementation
Right now, messages don't show on the padfoot side any more, but they
do get sent and received successfully on the other side, and empathy
can manage contacts now, so I'm calling it an overall win.
2020-05-21 02:30:47 +01:00
aae7607c7f Pin to the same version of rust nightly as delta 2020-05-19 00:16:01 +01:00
825f5d90ed Update compile and install instructions 2020-05-18 23:40:43 +01:00
a95be7ee4b Implement sending text messages
This works in a very simplistic way, but it's enough for two-way
communication. Hurrah!
2020-05-18 23:08:11 +01:00
72947bc99d Allow incoming messages to be seen by Empathy
MDNs don't work because the sender is not in a "real chat" yet- it's
coming from an unknown user - but we mark them as seen anyway.
2020-05-18 21:15:56 +01:00
b511dd873b Make text messages show on incoming 2020-05-18 19:37:35 +01:00
667eb3b3f6 By hook or by crook, amke empathy open a window 2020-05-18 01:57:22 +01:00
cb463336bc Rework automatic code generation 2020-05-17 23:23:45 +01:00
1e481d4c9a Wire up the additional channel interfaces a bit 2020-05-17 22:49:41 +01:00
e5e06c55f9 Reorder Channel::new args 2020-05-17 22:37:25 +01:00
576fec63cd Complete channel-closing behaviour
We now emit appropriate signals and return a non-error status code when
a channel is closed.
2020-05-17 22:23:57 +01:00
782662b82f Flesh out channels some more. They can now be closed.
As part of this, move Connection's queue to mpsc and have all channels
for a connection share that connection's main loop.
2020-05-17 22:05:24 +01:00
1eefce4f1c Create a channel on incoming messages
We don't yet emit signals, but we're getting closer.

This commit also partially implements the deprecated ListChannels
method on the Connection interface.
2020-05-17 15:20:16 +01:00
b814a9aab0 Partially on the way to receiving an incoming message
This is getting really ugly, but let's run with it for now.
2020-05-17 03:01:21 +01:00
49362a6606 Ignore lints in autogenerated code 2020-05-17 00:55:29 +01:00
7003b56ce6 A bit more progress on channels 2020-05-17 00:49:46 +01:00
25 changed files with 11478 additions and 865 deletions

898
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -9,8 +9,9 @@ license = "MIT"
[dependencies] [dependencies]
anyhow = "1.0" anyhow = "1.0"
async-std = "1.6"
dbus = "0.8" 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" directories = "2.0"
rand = "0.7" rand = "0.7"

148
README.md
View File

@@ -33,10 +33,10 @@ Here's where we're at right now:
- [x] Appear as online in Empathy - [x] Appear as online in Empathy
- [x] Disconnect! - [x] Disconnect!
- [ ] Set up an account manually - [ ] Set up an account manually
- [ ] Contacts handling - [~] Contacts handling
- [~] Text messages - [x] Text messages
- [ ] Multimedia messages - [ ] Multimedia messages
- [ ] Setup messages - [~] Setup messages
- [ ] Import/Export - [ ] Import/Export
- [ ] Group chats - [ ] Group chats
- [ ] Geolocation messages - [ ] Geolocation messages
@@ -52,9 +52,143 @@ learning exercise!
## How ## How
### Compiling
This project is written in Rust, so you'll need a rust compiler to build it. 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 [Rustup](https://github.com/rust-lang/rustup) comes highly recommended.
needs the `nightly` version, so follow the instructions for that.
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 It makes use of the `dbus-codegen-rust` crate to convert the
[telepathy interface specs](https://github.com/TelepathyIM/telepathy-spec) into [telepathy interface specs](https://github.com/TelepathyIM/telepathy-spec) into
@@ -64,10 +198,10 @@ regenerated like so:
```bash ```bash
$ git submodule init telepathy-spec $ git submodule init telepathy-spec
$ git submodule update telepathy-spec $ git submodule update telepathy-spec
$ cargo install dbus-codegen-rust $ cargo install dbus-codegen
$ ./scripts/dbus-codegen $ ./scripts/dbus-codegen
``` ```
`dbus-codegen-rust` doesn't seem to handle namespaced attributes properly, so `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` 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
View 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
View 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

File diff suppressed because it is too large Load Diff

1
rust-toolchain Normal file
View File

@@ -0,0 +1 @@
nightly-2020-03-12

View File

@@ -12,7 +12,7 @@ rm -f "$dest.rs"
rm -rf "$dest" rm -rf "$dest"
mkdir -p "$dest" mkdir -p "$dest"
echo "#![allow(unused)]\n#![allow(clippy::all)]" > "$modfile" echo "#![allow(unused)]" > "$modfile"
for file in $(ls -a $specs/*.xml); do for file in $(ls -a $specs/*.xml); do
sed -i 's/tp:type=/tp:typehint=/g' "$file" sed -i 's/tp:type=/tp:typehint=/g' "$file"
@@ -31,9 +31,9 @@ for file in $(ls -a $specs/*.xml); do
-a AsRefClosure \ -a AsRefClosure \
-o "$out" -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 done
git -C telepathy-spec checkout -- . git -C telepathy-spec checkout -- .

View File

@@ -5,21 +5,22 @@ BusName=org.freedesktop.Telepathy.ConnectionManager.padfoot
ObjectPath=/org/freedesktop/Telepathy/ConnectionManager/padfoot ObjectPath=/org/freedesktop/Telepathy/ConnectionManager/padfoot
[Protocol delta] [Protocol delta]
Interfaces=org.freedesktop.Telepathy.Protocol.Interface.Presence;
param-account=s required param-account=s required
param-password=s required secret param-password=s required secret
param-bcc-self=b
status-available=2 settable status-available=2 settable
status-offline = 1 settable status-offline = 1 settable
AuthenticationTypes=org.freedesktop.Telepathy.Channel.Type.ServerTLSConnection; AuthenticationTypes=org.freedesktop.Telepathy.Channel.Type.ServerTLSConnection;
ConnectionInterfaces=org.freedesktop.Telepathy.Connection.Interface.Avatars;org.freedesktop.Telepathy.Connection.Interface.Contacts;org.freedesktop.Telepathy.Connection.Interface.Requests;org.freedesktop.Telepathy.Connection.Interface.SimplePresence; 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 EnglishName=Delta Chat
Icon=im-delta Icon=im-delta
Interfaces= Interfaces=org.freedesktop.Telepathy.Protocol;org.freedesktop.Telepathy.Protocol.Interface.Presence;
RequestableChannelClasses=text; RequestableChannelClasses=text;
VCardField=email VCardField=email
[text] [text]
Interfaces=org.freedesktop.Telepathy.Channel.Interface.Messages;
org.freedesktop.Telepathy.Channel.ChannelType s=org.freedesktop.Telepathy.Channel.Type.Text org.freedesktop.Telepathy.Channel.ChannelType s=org.freedesktop.Telepathy.Channel.Type.Text
org.freedesktop.Telepathy.Channel.TargetHandleType u=1 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;

View File

@@ -7,6 +7,9 @@ pub use self::connection::*;
mod connection_manager; mod connection_manager;
pub use self::connection_manager::*; pub use self::connection_manager::*;
mod message;
pub use self::message::*;
mod protocol; mod protocol;
pub use self::protocol::*; pub use self::protocol::*;

View File

@@ -8,47 +8,190 @@ pub use messages::*;
mod type_text; mod type_text;
pub use type_text::*; pub use type_text::*;
use crate::padfoot::{var_bool, var_str, var_str_vec, var_u32, DbusAction, VarArg};
use crate::telepathy; 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. // FIXME: I'm assuming that all channels will be of type text and 1-1 for now.
#[derive(Debug)] #[derive(Debug)]
pub struct Channel { 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" // "This SHOULD NOT include the channel type and channel interface itself"
pub fn channel_interfaces() -> Vec<String> { pub fn channel_interfaces() -> Vec<String> {
vec![ vec!["org.freedesktop.Telepathy.Channel.Interface.Messages".to_string()]
"org.freedesktop.Telepathy.Channel.Interface.Messages".to_string(),
]
} }
type Result<T> = std::result::Result<T, dbus::tree::MethodErr>;
impl Channel { impl Channel {
fn build_tree(self) -> dbus::tree::Tree<dbus::tree::MTFn, ()> { pub fn new(
let c_rc = std::rc::Rc::new(self); actq: mpsc::Sender<DbusAction>,
let f = dbus::tree::Factory::new_fn::<()>(); chat_id: dc::chat::ChatId,
let mut tree = f.tree(()); 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,
}
}
let c_rc1 = c_rc.clone(); // 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 chan_iface = telepathy::channel_server(&f, (), move |_| c_rc1.clone());
let c_rc2 = c_rc.clone(); let c_rc2 = channel.clone();
let messages_iface = let messages_iface =
telepathy::channel_interface_messages_server(&f, (), move |_| c_rc2.clone()); telepathy::channel_interface_messages_server(&f, (), move |_| c_rc2.clone());
let type_text_iface = let c_rc3 = channel.clone();
telepathy::channel_type_text_server(&f, (), move |_| c_rc.clone()); let type_text_iface = telepathy::channel_type_text_server(&f, (), move |_| c_rc3.clone());
tree = tree.add( f.object_path(channel.path.clone(), ())
f.object_path("", ()) .introspectable()
.introspectable() .add(chan_iface)
.add(chan_iface) .add(messages_iface)
.add(messages_iface) .add(type_text_iface)
.add(type_text_iface), }
);
tree = tree.add(f.object_path("/", ()).introspectable());
tree 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);
}
} }
} }

View File

@@ -1,10 +1,11 @@
use crate::padfoot::DbusAction;
use crate::telepathy; use crate::telepathy;
use dbus::tree::MethodErr; use dbus::tree::MethodErr;
use super::{Channel, Result}; use super::{Channel, Result};
impl AsRef<dyn telepathy::Channel + 'static> for std::rc::Rc<Channel> { impl AsRef<dyn telepathy::Channel + 'static> for std::sync::Arc<Channel> {
fn as_ref(&self) -> &(dyn telepathy::Channel + 'static) { fn as_ref(&self) -> &(dyn telepathy::Channel + 'static) {
&**self &**self
} }
@@ -13,27 +14,37 @@ impl AsRef<dyn telepathy::Channel + 'static> for std::rc::Rc<Channel> {
impl telepathy::Channel for Channel { impl telepathy::Channel for Channel {
fn close(&self) -> Result<()> { fn close(&self) -> Result<()> {
println!("Channel::close()"); println!("Channel::close()");
Err(MethodErr::no_arg())
self.actq
.send(DbusAction::CloseChannel(self.path()))
.unwrap();
Ok(())
} }
// Deprecated
fn get_channel_type(&self) -> Result<String> { fn get_channel_type(&self) -> Result<String> {
self.channel_type() self.channel_type()
} }
fn get_handle(&self) -> Result<(u32, u32)> {
println!("Channel::get_handle()");
Err(MethodErr::no_arg())
}
fn get_interfaces(&self) -> Result<Vec<String>> {
println!("Channel::get_interfaces()");
Err(MethodErr::no_arg())
}
fn channel_type(&self) -> Result<String> { fn channel_type(&self) -> Result<String> {
println!("Channel::channel_type()"); println!("Channel::channel_type()");
Ok("org.freedesktop.Telepathy.Channel.Text".to_string()) 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>> { fn interfaces(&self) -> Result<Vec<String>> {
@@ -43,31 +54,45 @@ impl telepathy::Channel for Channel {
fn target_handle(&self) -> Result<u32> { fn target_handle(&self) -> Result<u32> {
println!("Channel::target_handle()"); println!("Channel::target_handle()");
Err(MethodErr::no_arg())
Ok(self.handle())
} }
fn target_id(&self) -> Result<String> { fn target_id(&self) -> Result<String> {
println!("Channel::target_id()"); println!("Channel::target_id()");
Err(MethodErr::no_arg())
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> { fn target_handle_type(&self) -> Result<u32> {
println!("Channel::target_handle_type()"); println!("Channel::target_handle_type()");
Err(MethodErr::no_arg())
Ok(self.handle_type())
} }
fn requested(&self) -> Result<bool> { fn requested(&self) -> Result<bool> {
println!("Channel::requested()"); println!("Channel::requested()");
Err(MethodErr::no_arg())
Ok(self.requested) // FIXME: channels initiated by ourselves *will* be requested
} }
fn initiator_handle(&self) -> Result<u32> { fn initiator_handle(&self) -> Result<u32> {
println!("Channel::initiator_handle()"); println!("Channel::initiator_handle()");
Err(MethodErr::no_arg())
self.target_handle() // FIXME: Not the case for channels initiated by ourselves
} }
fn initiator_id(&self) -> Result<String> { fn initiator_id(&self) -> Result<String> {
println!("Channel::initiator_id()"); println!("Channel::initiator_id()");
Err(MethodErr::no_arg())
if let Some(contact) = self.initiator_contact() {
Ok(contact.get_addr().to_string())
} else {
Err(MethodErr::no_arg())
}
} }
} }

View File

@@ -1,52 +1,139 @@
use crate::padfoot::VarArg; use crate::padfoot::{convert_msg, DbusAction, VarArg};
use crate::telepathy; use crate::telepathy;
use async_std::task::block_on;
use dbus::message::SignalArgs;
use dbus::tree::MethodErr; use dbus::tree::MethodErr;
use dc::constants::Viewtype;
use dc::message::{Message, MessageState};
use deltachat as dc;
use std::collections::HashMap; use std::collections::HashMap;
use super::{Channel, Result}; use super::{Channel, Result};
impl AsRef<dyn telepathy::ChannelInterfaceMessages + 'static> for std::rc::Rc<Channel> { impl AsRef<dyn telepathy::ChannelInterfaceMessages + 'static> for std::sync::Arc<Channel> {
fn as_ref(&self) -> &(dyn telepathy::ChannelInterfaceMessages + 'static) { fn as_ref(&self) -> &(dyn telepathy::ChannelInterfaceMessages + 'static) {
&**self &**self
} }
} }
impl telepathy::ChannelInterfaceMessages for Channel { impl telepathy::ChannelInterfaceMessages for Channel {
fn send_message(&self, message: Vec<HashMap<&str, VarArg>>, flags: u32) -> Result<String> { fn send_message(&self, parts: Vec<HashMap<&str, VarArg>>, flags: u32) -> Result<String> {
println!("Channel::send_message({:?}, {})", message, flags); println!("Channel::send_message({:?}, {})", parts, flags);
Err(MethodErr::no_arg())
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>> { fn get_pending_message_content(
println!("Channel::get_pending_message_content({}, {:?})", message_id, parts); &self,
Err(MethodErr::no_arg()) 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>> { fn supported_content_types(&self) -> Result<Vec<String>> {
println!("Channel::supported_content_types()"); println!("Channel::supported_content_types()");
Err(MethodErr::no_arg())
Ok(vec!["*/*".to_string()])
} }
fn message_types(&self) -> Result<Vec<u32>> { fn message_types(&self) -> Result<Vec<u32>> {
println!("Channel::message_types()"); println!("Channel::message_types()");
Err(MethodErr::no_arg())
Ok(vec![0]) // Normal messages. FIXME: MDNs too
} }
fn message_part_support_flags(&self) -> Result<u32> { fn message_part_support_flags(&self) -> Result<u32> {
println!("Channel::message_part_support_flags()"); println!("Channel::message_part_support_flags()");
Err(MethodErr::no_arg())
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>>>> { fn pending_messages(&self) -> Result<Vec<Vec<HashMap<String, VarArg>>>> {
println!("Channel::pending_messages()"); println!("Channel::pending_messages()");
Err(MethodErr::no_arg())
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> { fn delivery_reporting_support(&self) -> Result<u32> {
println!("Channel::delivery_reporting_support()"); println!("Channel::delivery_reporting_support()");
Err(MethodErr::no_arg())
Ok(0) // FIXME: MDNs
} }
} }

View File

@@ -1,38 +1,69 @@
use crate::padfoot::DbusAction;
use crate::telepathy; use crate::telepathy;
use crate::telepathy::ChannelInterfaceMessages;
use async_std::task::block_on;
use dbus::message::SignalArgs;
use dbus::tree::MethodErr; use dbus::tree::MethodErr;
use dc::message::MsgId;
use deltachat as dc;
use super::{Channel, Result}; use super::{Channel, Result};
impl AsRef<dyn telepathy::ChannelTypeText + 'static> for std::rc::Rc<Channel> { impl AsRef<dyn telepathy::ChannelTypeText + 'static> for std::sync::Arc<Channel> {
fn as_ref(&self) -> &(dyn telepathy::ChannelTypeText + 'static) { fn as_ref(&self) -> &(dyn telepathy::ChannelTypeText + 'static) {
&**self &**self
} }
} }
type PendingMessagesSpec = ( type PendingMessagesSpec = (
u32, // numeric identifier u32, // numeric identifier
u32, // Unix timestamp indicating when the message was received u32, // Unix timestamp indicating when the message was received
u32, // contact handle for the contact who sent the message u32, // contact handle for the contact who sent the message
u32, // message type, taken from ChannelTextMessageType u32, // message type, taken from ChannelTextMessageType
u32, // bitwise-OR of the message flags from ChannelTextMessageFlags u32, // bitwise-OR of the message flags from ChannelTextMessageFlags
String, // text of the message String, // text of the message
); );
// Most of these methods are deprecated, so should be implemented in terms of // Most of these methods are deprecated, so should be implemented in terms of
// the mandatory Messages interface. // the mandatory Messages interface.
impl telepathy::ChannelTypeText for Channel { impl telepathy::ChannelTypeText for Channel {
// ids is a list of deltachat msg_ids
fn acknowledge_pending_messages(&self, ids: Vec<u32>) -> Result<()> { fn acknowledge_pending_messages(&self, ids: Vec<u32>) -> Result<()> {
println!("Channel::acknowledge_pending_messages({:?})", ids); println!("Channel::acknowledge_pending_messages({:?})", ids);
Err(MethodErr::no_arg())
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>> { fn get_message_types(&self) -> Result<Vec<u32>> {
println!("Channel::get_message_types()"); println!("Channel::get_message_types()");
Err(MethodErr::no_arg())
self.message_types()
} }
fn list_pending_messages(&self, clear: bool) -> Result<Vec<PendingMessagesSpec>> { fn list_pending_messages(&self, clear: bool) -> Result<Vec<PendingMessagesSpec>> {
println!("Channel::list_pending_messages({})", clear); println!("Channel::list_pending_messages({})", clear);
Err(MethodErr::no_arg()) Err(MethodErr::no_arg())
} }

View File

@@ -20,11 +20,13 @@ pub use self::requests::*;
mod simple_presence; mod simple_presence;
pub use self::simple_presence::*; pub use self::simple_presence::*;
use crate::padfoot::VarArg; use crate::padfoot::{convert_msg, Channel, VarArg};
use crate::telepathy; use crate::telepathy;
use async_std::task::block_on;
use dbus::blocking::{stdintf::org_freedesktop_dbus::RequestNameReply, LocalConnection}; use dbus::blocking::{stdintf::org_freedesktop_dbus::RequestNameReply, LocalConnection};
use dbus::channel::Sender; use dbus::channel::{MatchingReceiver, Sender};
use dbus::message::SignalArgs;
use dbus::tree::MethodErr; use dbus::tree::MethodErr;
use dc::config::Config; use dc::config::Config;
@@ -32,31 +34,50 @@ use dc::context::Context;
use dc::Event; use dc::Event;
use deltachat as dc; use deltachat as dc;
use std::collections::{HashMap, HashSet, VecDeque}; use std::collections::{HashMap, HashSet};
use std::sync::{mpsc, Arc, Mutex, RwLock}; use std::sync::{mpsc, Arc, Mutex, RwLock};
use std::thread;
use std::time::Duration; use std::time::Duration;
pub const CONN_BUS_NAME: &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"; 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)] #[derive(Debug)]
// A Deltachast connection uses email addresses as handles, and delta's Db IDs pub enum DbusAction {
pub struct Connection { Signal(dbus::Message), // Generic signal to send
// Remove ourselves from this when done
conns: Arc<Mutex<HashSet<String>>>,
ctx: Arc<RwLock<Context>>, 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, settings: ConnSettings,
state: Arc<RwLock<ConnState>>, state: Arc<RwLock<ConnState>>,
// Used for sending out messages
msgq: Arc<Mutex<VecDeque<dbus::Message>>>,
} }
#[derive(Debug)] #[derive(Debug)]
pub struct ConnSettings { pub struct ConnSettings {
account: String, account: String,
password: String, password: String,
bcc_self: bool,
id: String, id: String,
} }
@@ -82,9 +103,24 @@ impl ConnSettings {
None => return err, None => return err,
}; };
let bcc_self = match params.get("bcc-self") {
Some(variant) => match variant.0.as_u64() {
Some(i) => i != 0,
None => {
println!("0!");
return err;
}
},
None => {
println!("1!");
false
}
};
Ok(Self { Ok(Self {
account, account,
password, password,
bcc_self,
id, id,
}) })
} }
@@ -97,94 +133,130 @@ impl ConnSettings {
CONN_BUS_NAME.to_owned() + "." + &self.id CONN_BUS_NAME.to_owned() + "." + &self.id
} }
pub fn path(&self) -> String { pub fn path(&self) -> dbus::Path<'static> {
CONN_OBJECT_PATH.to_owned() + "/" + &self.id dbus::Path::new(format!("{}/{}", CONN_OBJECT_PATH, &self.id)).expect("Valid path")
} }
} }
impl Connection { impl Connection {
pub fn new( pub fn new(
settings: ConnSettings, settings: ConnSettings,
conns: Arc<Mutex<HashSet<String>>>, conns: Arc<Mutex<HashSet<dbus::Path<'static>>>>,
) -> Result<Self, MethodErr> { ) -> Result<(Self, mpsc::Receiver<DbusAction>), MethodErr> {
let mut dbfile = directories::ProjectDirs::from("gs", "ur", "telepathy-padfoot") let proj_dir = directories::ProjectDirs::from("gs", "ur", "telepathy-padfoot")
.ok_or_else(MethodErr::no_arg) .ok_or_else(MethodErr::no_arg)?;
.and_then(|p| Ok(p.data_local_dir().to_path_buf()))?;
let mut dbfile = async_std::path::PathBuf::new();
dbfile.push(proj_dir.data_local_dir().to_str().unwrap());
dbfile.push(settings.id()); dbfile.push(settings.id());
dbfile.push("db.sqlite3"); dbfile.push("db.sqlite3");
// FIXME: how to give it access to the connection (initialized later)? let (q_s, q_r) = mpsc::channel::<DbusAction>();
let msgq = Arc::new(Mutex::new(VecDeque::<dbus::Message>::new()));
let id = settings.id(); let id = settings.id();
// Use this if we need to send messages in response to DC events:
// let msgq2 = msgq.clone();
let f = move |_c: &Context, e: Event| {
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);
}
/* Unhandled messages:
SmtpMessageSent(String),
ImapMessageDeleted(String),
ImapMessageMoved(String),
ImapFolderEmptied(String),
NewBlobFile(String),
DeletedBlobFile(String),
MsgsChanged
IncomingMsg
MsgDelivered
MsgFailed
MsgRead
ChatModified(ChatId),
ContactsChanged(Option<u32>),
LocationChanged(Option<u32>),
ImexProgress(usize),
ImexFileWritten(PathBuf),
SecurejoinInviterProgress
SecurejoinJoinerProgress
*/
_ => println!("Connection<{}>: unhandled event received: {:?}", id, e),
};
};
let ctx = let ctx = Arc::new(
Context::new(Box::new(f), "telepathy-padfoot".to_string(), dbfile).map_err(|e| { block_on(Context::new("telepathy-padfoot".to_string(), dbfile)).map_err(|e| {
println!( println!(
"Connection<{}>::new(): couldn't get delta context: {}", "Connection<{}>::new(): couldn't get delta context: {}",
settings.id(), settings.id(),
e e
); );
MethodErr::no_arg() // FIXME: better error handling MethodErr::no_arg() // FIXME: better error handling
})?; })?,
);
ctx.set_config(Config::Addr, Some(&settings.account)) 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())?; .map_err(|_e| MethodErr::no_arg())?;
ctx.set_config(Config::MailPw, Some(&settings.password)) block_on(ctx.set_config(Config::MailPw, Some(&settings.password)))
.map_err(|_e| MethodErr::no_arg())?;
ctx.set_config(Config::SentboxWatch, Some(&"Sent"))
.map_err(|_e| MethodErr::no_arg())?; .map_err(|_e| MethodErr::no_arg())?;
if !ctx.is_configured() { if settings.bcc_self {
ctx.configure(); 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 { Ok((
conns, Connection {
settings, actq: q_s,
msgq, channels: Arc::new(RwLock::new(
ctx: Arc::new(RwLock::new(ctx)), HashMap::<dbus::Path<'static>, Arc<Channel>>::new(),
state: Arc::new(RwLock::new(ConnState::Initial)), )),
}) 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 // This should be run inside its own thread. It will signal via the channel
@@ -192,13 +264,19 @@ impl Connection {
// //
// FIXME: running several +process+ loops sure is convenient, but it also // FIXME: running several +process+ loops sure is convenient, but it also
// seems inefficient... // seems inefficient...
pub fn run(self, signal: mpsc::Sender<Option<MethodErr>>) { pub fn run(
self,
done_signal: mpsc::Sender<Option<MethodErr>>,
queue_receiver: mpsc::Receiver<DbusAction>,
) {
let id = self.id(); let id = self.id();
let bus = self.bus(); let bus = self.bus();
let path = self.path(); let path = self.path();
let conns = self.conns.clone(); let conns = self.conns.clone();
let msgq = self.msgq.clone(); let chans = self.channels.clone();
let actq = self.actq.clone();
let ctx = self.ctx.clone();
let state = self.state.clone(); let state = self.state.clone();
let tree = self.build_tree(); let tree = self.build_tree();
@@ -211,24 +289,36 @@ 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) { match c.request_name(bus.clone(), false, false, true) {
Ok(RequestNameReply::Exists) => { Ok(RequestNameReply::Exists) => {
println!("Another process is already registered on {}", bus); println!("Another process is already registered on {}", bus);
signal.send(Some(MethodErr::no_arg())).unwrap(); done_signal.send(Some(MethodErr::no_arg())).unwrap();
return; return;
} }
Err(e) => { Err(e) => {
println!("Failed to register {}: {}", bus, e); println!("Failed to register {}: {}", bus, e);
signal.send(Some(MethodErr::no_arg())).unwrap(); done_signal.send(Some(MethodErr::no_arg())).unwrap();
return; return;
} }
_ => { _ => {
// All other responses we can get are a success. We are now on // All other responses we can get are a success. We are now on
// the message bus, so the caller can proceed // the message bus, so the caller can proceed
println!("{} listening on {}", c.unique_name(), bus); println!("{} listening on {}", c.unique_name(), bus);
signal.send(None).unwrap(); done_signal.send(None).unwrap();
} }
}; };
@@ -242,12 +332,168 @@ impl Connection {
}; };
// Spend a bit of time sending any outgoing messages - signals, mostly // Spend a bit of time sending any outgoing messages - signals, mostly
while let Some(msg) = msgq.lock().unwrap().pop_front() { while let Some(act) = queue_receiver.try_recv().ok() {
print!("Connection<{}>: Sending message...", id); match act {
DbusAction::Signal(msg) => {
print!("*** Connection<{}>: Sending signal: {:?}...", id, msg);
match c.send(msg) { match c.send(msg) {
Err(e) => println!("error! {:?}", e), // FIXME: handle error better? Err(e) => println!("error! {:?}", e), // FIXME: handle error better?
_ => println!("OK!"), _ => 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),
}
}
}
} }
} }
} }
@@ -266,11 +512,20 @@ impl Connection {
self.settings.bus() self.settings.bus()
} }
pub fn path(&self) -> String { pub fn path(&self) -> dbus::Path<'static> {
self.settings.path() self.settings.path()
} }
fn build_tree(self) -> dbus::tree::Tree<dbus::tree::MTFn, ()> { 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 path = self.path();
let c_rc = std::rc::Rc::new(self); let c_rc = std::rc::Rc::new(self);
let f = dbus::tree::Factory::new_fn::<()>(); let f = dbus::tree::Factory::new_fn::<()>();
@@ -288,7 +543,7 @@ impl Connection {
telepathy::connection_interface_contacts_server(&f, (), move |_| c_rc3.clone()); telepathy::connection_interface_contacts_server(&f, (), move |_| c_rc3.clone());
let _c_rc4 = c_rc.clone(); let _c_rc4 = c_rc.clone();
let _contact_list_iface = let contact_list_iface =
telepathy::connection_interface_contact_list_server(&f, (), move |_| _c_rc4.clone()); telepathy::connection_interface_contact_list_server(&f, (), move |_| _c_rc4.clone());
let c_rc5 = c_rc.clone(); let c_rc5 = c_rc.clone();
@@ -304,12 +559,12 @@ impl Connection {
.add(conn_iface) .add(conn_iface)
.add(avatars_iface) .add(avatars_iface)
.add(contacts_iface) .add(contacts_iface)
// .add(contact_list_iface) .add(contact_list_iface)
.add(requests_iface) .add(requests_iface)
.add(simple_presence_iface), .add(simple_presence_iface),
); );
tree = tree.add(f.object_path("/", ()).introspectable()); tree = tree.add(f.object_path("/", ()).introspectable());
tree Arc::new(Mutex::new(tree))
} }
} }

View File

@@ -1,11 +1,17 @@
use crate::telepathy; use crate::telepathy;
use crate::telepathy::{ConnectionInterfaceContacts, ConnectionInterfaceRequests}; // Non-deprecated channel methods
use async_std::task::block_on;
use dbus::message::SignalArgs; use dbus::message::SignalArgs;
use dbus::tree::MethodErr; use dbus::tree::MethodErr;
use dc::contact::Contact;
use deltachat as dc; use deltachat as dc;
use std::collections::HashMap;
use std::convert::TryInto;
use std::thread; use std::thread;
use super::Connection; use super::{Connection, DbusAction};
#[derive(Debug, PartialEq, Eq)] #[derive(Debug, PartialEq, Eq)]
pub enum ConnState { pub enum ConnState {
@@ -14,12 +20,28 @@ pub enum ConnState {
Disconnected, 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> { pub fn connection_interfaces() -> Vec<String> {
vec![ vec![
"org.freedesktop.Telepathy.Connection".to_string(), "org.freedesktop.Telepathy.Connection".to_string(),
"org.freedesktop.Telepathy.Connection.Interface.Avatars".to_string(), "org.freedesktop.Telepathy.Connection.Interface.Avatars".to_string(),
"org.freedesktop.Telepathy.Connection.Interface.Contacts".to_string(), "org.freedesktop.Telepathy.Connection.Interface.Contacts".to_string(),
// "org.freedesktop.Telepathy.Connection.Interface.ContactList".to_string(), "org.freedesktop.Telepathy.Connection.Interface.ContactList".to_string(),
"org.freedesktop.Telepathy.Connection.Interface.Requests".to_string(), "org.freedesktop.Telepathy.Connection.Interface.Requests".to_string(),
"org.freedesktop.Telepathy.Connection.Interface.SimplePresence".to_string(), "org.freedesktop.Telepathy.Connection.Interface.SimplePresence".to_string(),
] ]
@@ -33,67 +55,15 @@ impl AsRef<dyn telepathy::Connection + 'static> for std::rc::Rc<Connection> {
impl telepathy::Connection for Connection { impl telepathy::Connection for Connection {
// In connect(), we start the threads that drive the deltachat context // In connect(), we start the threads that drive the deltachat context
fn connect(&self) -> Result<(), MethodErr> { fn connect(&self) -> Result<()> {
println!("Connection<{}>::connect()", self.id()); println!("Connection<{}>::connect()", self.id());
let inbox_ctx = self.ctx.clone(); let io_ctx = self.ctx.clone();
let state = self.state.clone(); let io_id = self.id();
let id = self.id(); let _io_thread = thread::spawn(move || {
let _inbox_thread = thread::spawn(move || { block_on(io_ctx.start_io());
while *state.read().unwrap() != ConnState::Disconnected {
dc::job::perform_inbox_jobs(&inbox_ctx.read().unwrap());
if *state.read().unwrap() != ConnState::Disconnected {
dc::job::perform_inbox_fetch(&inbox_ctx.read().unwrap());
if *state.read().unwrap() != ConnState::Disconnected { println!("Connection<{}>::connect(): I/O thread exited", io_id);
dc::job::perform_inbox_idle(&inbox_ctx.read().unwrap());
}
}
}
println!("Connection<{}>::connect(): inbox thread exited", id);
});
let smtp_ctx = self.ctx.clone();
let state = self.state.clone();
let id = self.id();
let _smtp_thread = thread::spawn(move || {
while *state.read().unwrap() != ConnState::Disconnected {
dc::job::perform_smtp_jobs(&smtp_ctx.read().unwrap());
if *state.read().unwrap() != ConnState::Disconnected {
dc::job::perform_smtp_idle(&smtp_ctx.read().unwrap());
}
}
println!("Connection<{}>::connect(): smtp thread exited", id);
});
let mvbox_ctx = self.ctx.clone();
let state = self.state.clone();
let id = self.id();
let _mvbox_thread = thread::spawn(move || {
while *state.read().unwrap() != ConnState::Disconnected {
dc::job::perform_mvbox_fetch(&mvbox_ctx.read().unwrap());
if *state.read().unwrap() != ConnState::Disconnected {
dc::job::perform_mvbox_idle(&mvbox_ctx.read().unwrap());
}
}
println!("Connection<{}>::connect(): mvbox thread exited", id);
});
let sentbox_ctx = self.ctx.clone();
let state = self.state.clone();
let id = self.id();
let _sentbox_thread = thread::spawn(move || {
while *state.read().unwrap() != ConnState::Disconnected {
dc::job::perform_sentbox_fetch(&sentbox_ctx.read().unwrap());
if *state.read().unwrap() != ConnState::Disconnected {
dc::job::perform_sentbox_idle(&sentbox_ctx.read().unwrap());
}
}
println!("Connection<{}>::connect(): sentbox thread exited", id);
}); });
// Just pretend to be connected all the time for now. Tracking IMAP+SMTP // Just pretend to be connected all the time for now. Tracking IMAP+SMTP
@@ -101,88 +71,111 @@ impl telepathy::Connection for Connection {
let state = self.state.clone(); let state = self.state.clone();
let mut w = state.write().unwrap(); let mut w = state.write().unwrap();
*w = ConnState::Connected; *w = ConnState::Connected;
let ctx = self.ctx.clone();
// Emit a StatusChanged signal for the benefit of others, but the caller // Emit a StatusChanged signal for the benefit of others, but the caller
// learns from our RPC response // learns from our RPC response
let sig = telepathy::ConnectionStatusChanged { let connected_sig = telepathy::ConnectionStatusChanged {
status: 0, // Connected status: 0, // Connected
reason: 1, // Requested 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();
}; };
let dbus_conn_path = dbus::strings::Path::new(self.path())
.expect("Object path should meet DBUS requirements");
self.msgq
.lock()
.unwrap()
.push_back(sig.to_emit_message(&dbus_conn_path));
Ok(()) Ok(())
} }
fn disconnect(&self) -> Result<(), MethodErr> { fn disconnect(&self) -> Result<()> {
println!("Connection<{}>::disconnect()", self.id()); println!("Connection<{}>::disconnect()", self.id());
let ctx = self.ctx.read().unwrap(); block_on(self.ctx.stop_io());
let state = self.state.clone(); let state = self.state.clone();
let mut w = state.write().unwrap(); let mut w = state.write().unwrap();
*w = ConnState::Disconnected; *w = ConnState::Disconnected;
dc::job::interrupt_inbox_idle(&ctx);
dc::job::interrupt_smtp_idle(&ctx);
dc::job::interrupt_sentbox_idle(&ctx);
dc::job::interrupt_mvbox_idle(&ctx);
// FIXME: we need to signal to the CM that they should remove the // FIXME: we need to signal to the CM that they should remove the
// connection from the active list // connection from the active list
Ok(()) Ok(())
} }
fn interfaces(&self) -> Result<Vec<String>, MethodErr> { fn interfaces(&self) -> Result<Vec<String>> {
println!("Connection<{}>::interfaces()", self.id()); println!("Connection<{}>::interfaces()", self.id());
self.get_interfaces() self.get_interfaces()
} }
fn get_interfaces(&self) -> Result<Vec<String>, MethodErr> { fn get_interfaces(&self) -> Result<Vec<String>> {
println!("Connection<{}>::get_interfaces()", self.id()); println!("Connection<{}>::get_interfaces()", self.id());
Ok(connection_interfaces()) Ok(connection_interfaces())
} }
fn get_protocol(&self) -> Result<String, MethodErr> { fn get_protocol(&self) -> Result<String> {
println!("Connection<{}>::get_protocol()", self.id()); println!("Connection<{}>::get_protocol()", self.id());
Ok(crate::padfoot::PROTO_NAME.to_string()) Ok(crate::padfoot::PROTO_NAME.to_string())
} }
fn self_handle(&self) -> Result<u32, MethodErr> { fn self_handle(&self) -> Result<u32> {
println!("Connection<{}>::self_handle()", self.id()); println!("Connection<{}>::self_handle()", self.id());
self.get_self_handle()
}
fn get_self_handle(&self) -> Result<u32, MethodErr> {
println!("Connection<{}>::get_self_handle()", self.id());
Ok(dc::constants::DC_CONTACT_ID_SELF) Ok(dc::constants::DC_CONTACT_ID_SELF)
} }
fn status(&self) -> Result<u32, MethodErr> { // 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()); println!("Connection<{}>::status()", self.id());
self.get_status() self.get_status()
} }
fn get_status(&self) -> Result<u32, MethodErr> { fn get_status(&self) -> Result<u32> {
match *self.state.clone().read().unwrap() { match *self.state.clone().read().unwrap() {
ConnState::Initial | ConnState::Disconnected => Ok(2), ConnState::Initial | ConnState::Disconnected => Ok(2),
ConnState::Connected => Ok(0), ConnState::Connected => Ok(0),
} }
} }
fn hold_handles(&self, handle_type: u32, handles: Vec<u32>) -> Result<(), MethodErr> { fn hold_handles(&self, handle_type: u32, handles: Vec<u32>) -> Result<()> {
println!( println!(
"Connection<{}>::hold_handles({}, {:?})", "Connection<{}>::hold_handles({}, {:?})",
self.id(), self.id(),
@@ -194,26 +187,52 @@ impl telepathy::Connection for Connection {
Ok(()) Ok(())
} }
fn inspect_handles( fn inspect_handles(&self, handle_type: u32, handles: Vec<u32>) -> Result<Vec<String>> {
&self,
handle_type: u32,
handles: Vec<u32>,
) -> Result<Vec<String>, MethodErr> {
println!( println!(
"Connection<{}>::inspect_handles({}, {:?})", "Connection<{}>::inspect_handles({}, {:?})",
self.id(), self.id(),
handle_type, handle_type,
handles handles
); );
Err(MethodErr::no_arg()) // FIXME: should be NotImplemented? 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?
}
} }
fn list_channels(&self) -> Result<Vec<(dbus::Path<'static>, String, u32, u32)>, MethodErr> { // Deprecated in favour of Requests.Channels
fn list_channels(&self) -> Result<Vec<ChannelInfo>> {
println!("Connection<{}>::list_channels()", self.id()); println!("Connection<{}>::list_channels()", self.id());
Err(MethodErr::no_arg()) // FIXME: should be NotImplemented?
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<(), MethodErr> { fn release_handles(&self, handle_type: u32, handles: Vec<u32>) -> Result<()> {
println!( println!(
"Connection<{}>::release_handles({}, {:?})", "Connection<{}>::release_handles({}, {:?})",
self.id(), self.id(),
@@ -225,39 +244,74 @@ impl telepathy::Connection for Connection {
Ok(()) Ok(())
} }
// RequestChannel is deprecated in favour of the Requests interface.
fn request_channel( fn request_channel(
&self, &self,
type_: &str, channel_type: &str,
handle_type: u32, handle_type: u32,
handle: u32, handle: u32,
suppress_handler: bool, suppress_handler: bool,
) -> Result<dbus::Path<'static>, MethodErr> { ) -> Result<dbus::Path<'static>> {
println!( println!(
"Connection<{}>::request_channel({}, {}, {}, {})", "Connection<{}>::request_channel({}, {}, {}, {})",
self.id(), self.id(),
type_, channel_type,
handle_type, handle_type,
handle, handle,
suppress_handler suppress_handler
); );
Err(MethodErr::no_arg()) // FIXME: should be NotImplemented? Err(MethodErr::no_arg()) // FIXME: should be NotImplemented?
} }
fn request_handles( fn request_handles(&self, handle_type: u32, identifiers: Vec<&str>) -> Result<Vec<u32>> {
&self,
handle_type: u32,
identifiers: Vec<&str>,
) -> Result<Vec<u32>, MethodErr> {
println!( println!(
"Connection<{}>::request_handles({}, {:?})", "Connection<{}>::request_handles({}, {:?})",
self.id(), self.id(),
handle_type, handle_type,
identifiers identifiers
); );
Err(MethodErr::no_arg()) // FIXME: should be NotImplemented?
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<(), MethodErr> { fn add_client_interest(&self, tokens: Vec<&str>) -> Result<()> {
println!( println!(
"Connection<{}>::add_client_interest({:?})", "Connection<{}>::add_client_interest({:?})",
self.id(), self.id(),
@@ -266,7 +320,7 @@ impl telepathy::Connection for Connection {
Err(MethodErr::no_arg()) // FIXME: should be NotImplemented? Err(MethodErr::no_arg()) // FIXME: should be NotImplemented?
} }
fn remove_client_interest(&self, tokens: Vec<&str>) -> Result<(), MethodErr> { fn remove_client_interest(&self, tokens: Vec<&str>) -> Result<()> {
println!( println!(
"Connection<{}>::remove_client_interest({:?})", "Connection<{}>::remove_client_interest({:?})",
self.id(), self.id(),
@@ -275,13 +329,13 @@ impl telepathy::Connection for Connection {
Err(MethodErr::no_arg()) // FIXME: should be NotImplemented? Err(MethodErr::no_arg()) // FIXME: should be NotImplemented?
} }
fn self_id(&self) -> Result<String, MethodErr> { fn self_id(&self) -> Result<String> {
println!("Connection<{}>::self_id()", self.id()); println!("Connection<{}>::self_id()", self.id());
let contact = match dc::contact::Contact::get_by_id( let contact = match block_on(dc::contact::Contact::get_by_id(
&self.ctx.read().unwrap(), &self.ctx,
dc::constants::DC_CONTACT_ID_SELF, dc::constants::DC_CONTACT_ID_SELF,
) { )) {
Ok(c) => c, Ok(c) => c,
Err(e) => { Err(e) => {
println!(" err: {}", e); println!(" err: {}", e);
@@ -292,7 +346,7 @@ impl telepathy::Connection for Connection {
Ok(contact.get_addr().to_string()) Ok(contact.get_addr().to_string())
} }
fn has_immortal_handles(&self) -> Result<bool, MethodErr> { fn has_immortal_handles(&self) -> Result<bool> {
println!("Connection<{}>::has_immortal_handles()", self.id()); println!("Connection<{}>::has_immortal_handles()", self.id());
Ok(true) Ok(true)

View File

@@ -1,9 +1,11 @@
use crate::padfoot::VarArg; use crate::padfoot::VarArg;
use crate::telepathy; use crate::telepathy;
use async_std::task::block_on;
use dbus::tree::MethodErr; use dbus::tree::MethodErr;
use deltachat::constants::DC_GCL_ADD_SELF; use dc::constants::DC_GCL_ADD_SELF;
use deltachat::contact::Contact; use dc::contact::Contact;
use deltachat as dc;
use std::collections::HashMap; use std::collections::HashMap;
use std::convert::TryInto; use std::convert::TryInto;
use telepathy::ConnectionInterfaceContacts; // for get_contact_attributes use telepathy::ConnectionInterfaceContacts; // for get_contact_attributes
@@ -30,8 +32,11 @@ impl telepathy::ConnectionInterfaceContactList for Connection {
hold hold
); );
let ctx = &self.ctx.read().unwrap(); let ids = match block_on(Contact::get_all(
let ids = match Contact::get_all(ctx, DC_GCL_ADD_SELF.try_into().unwrap(), None::<String>) { &self.ctx,
DC_GCL_ADD_SELF.try_into().unwrap(),
None::<String>,
)) {
Ok(c) => c, Ok(c) => c,
Err(e) => { Err(e) => {
println!(" err: {}", e); println!(" err: {}", e);
@@ -39,6 +44,7 @@ impl telepathy::ConnectionInterfaceContactList for Connection {
} }
}; };
// FIXME: swap implementations so Contacts depends on ContactList?
self.get_contact_attributes(ids, vec![], true) self.get_contact_attributes(ids, vec![], true)
} }
@@ -62,6 +68,17 @@ impl telepathy::ConnectionInterfaceContactList for Connection {
} }
fn remove_contacts(&self, contacts: Vec<u32>) -> Result<(), MethodErr> { fn remove_contacts(&self, contacts: Vec<u32>) -> Result<(), MethodErr> {
println!("Connection<{}>::remove_contacts({:?})", self.id(), contacts); 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(()) Ok(())
} }
fn unsubscribe(&self, contacts: Vec<u32>) -> Result<(), MethodErr> { fn unsubscribe(&self, contacts: Vec<u32>) -> Result<(), MethodErr> {
@@ -76,6 +93,8 @@ impl telepathy::ConnectionInterfaceContactList for Connection {
fn download(&self) -> Result<(), MethodErr> { fn download(&self) -> Result<(), MethodErr> {
println!("Connection<{}>::download()", self.id()); println!("Connection<{}>::download()", self.id());
// This can be a no-op since we store the contacts list in delta's DB
Ok(()) Ok(())
} }
@@ -91,7 +110,7 @@ impl telepathy::ConnectionInterfaceContactList for Connection {
fn can_change_contact_list(&self) -> Result<bool, MethodErr> { fn can_change_contact_list(&self) -> Result<bool, MethodErr> {
println!("Connection<{}>::can_change_contact_list()", self.id()); println!("Connection<{}>::can_change_contact_list()", self.id());
Ok(false) Ok(true)
} }
fn request_uses_message(&self) -> Result<bool, MethodErr> { fn request_uses_message(&self) -> Result<bool, MethodErr> {
println!("Connection<{}>::request_uses_message()", self.id()); println!("Connection<{}>::request_uses_message()", self.id());
@@ -100,6 +119,11 @@ impl telepathy::ConnectionInterfaceContactList for Connection {
fn download_at_connection(&self) -> Result<bool, MethodErr> { fn download_at_connection(&self) -> Result<bool, MethodErr> {
println!("Connection<{}>::download_at_connection()", self.id()); println!("Connection<{}>::download_at_connection()", self.id());
Ok(false)
// 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)
} }
} }

View File

@@ -1,6 +1,7 @@
use crate::padfoot::{var_str, VarArg}; use crate::padfoot::{var_str, var_u32, VarArg};
use crate::telepathy; use crate::telepathy;
use async_std::task::block_on;
use dbus::tree::MethodErr; use dbus::tree::MethodErr;
use deltachat::contact::{Contact, Origin}; use deltachat::contact::{Contact, Origin};
use std::collections::HashMap; use std::collections::HashMap;
@@ -31,7 +32,7 @@ impl telepathy::ConnectionInterfaceContacts for Connection {
let mut out = HashMap::<u32, HashMap<String, VarArg>>::new(); let mut out = HashMap::<u32, HashMap<String, VarArg>>::new();
for id in handles.iter() { for id in handles.iter() {
// FIXME: work out how to use get_all // FIXME: work out how to use get_all
let contact = match Contact::get_by_id(&self.ctx.read().unwrap(), *id) { let contact = match block_on(Contact::get_by_id(&self.ctx, *id)) {
Ok(c) => c, Ok(c) => c,
Err(_e) => continue, // Invalid IDs are silently ignored Err(_e) => continue, // Invalid IDs are silently ignored
}; };
@@ -48,18 +49,17 @@ impl telepathy::ConnectionInterfaceContacts for Connection {
"org.freedesktop.Telepathy.Connection.Interface.Avatars/token".to_string(), "org.freedesktop.Telepathy.Connection.Interface.Avatars/token".to_string(),
var_str("".to_string()), var_str("".to_string()),
); );
/*
// TODO: we need to publish DBUS services on these endpoints
props.insert(
"org.freedesktop.Telepathy.Connection.Interface.ContactList/publish".to_string(),
var_arg(Box::new(4)),
);
props.insert( props.insert(
"org.freedesktop.Telepathy.Connection.Interface.ContactList/subscribe".to_string(), "org.freedesktop.Telepathy.Connection.Interface.ContactList/publish".to_string(),
var_arg(Box::new(4)), 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); out.insert(*id, props);
} }
@@ -78,10 +78,11 @@ impl telepathy::ConnectionInterfaceContacts for Connection {
interfaces interfaces
); );
let id = { let id = block_on(Contact::lookup_id_by_addr(
let ctx = &self.ctx.read().unwrap(); &self.ctx,
Contact::lookup_id_by_addr(ctx, identifier, Origin::Unknown) identifier,
}; Origin::Unknown,
));
if id == 0 { if id == 0 {
return Err(MethodErr::no_arg()); // FIXME: should be InvalidHandle return Err(MethodErr::no_arg()); // FIXME: should be InvalidHandle
@@ -101,7 +102,7 @@ impl telepathy::ConnectionInterfaceContacts for Connection {
Ok(vec![ Ok(vec![
"org.freedesktop.Telepathy.Connection".to_string(), "org.freedesktop.Telepathy.Connection".to_string(),
"org.freedesktop.Telepathy.Connection.Interface.Avatars".to_string(), "org.freedesktop.Telepathy.Connection.Interface.Avatars".to_string(),
// "org.freedesktop.Telepathy.Connection.Interface.ContactList".to_string(), "org.freedesktop.Telepathy.Connection.Interface.ContactList".to_string(),
]) ])
} }
} }

View File

@@ -1,26 +1,35 @@
use crate::padfoot::{requestables, VarArg}; use crate::padfoot::{get_var_str, get_var_u32, requestables, Channel, DbusAction, VarArg};
use crate::telepathy; use crate::telepathy;
use async_std::task::block_on;
use dbus::tree::MethodErr; use dbus::tree::MethodErr;
use dc::contact::Contact;
use deltachat as dc;
use std::collections::HashMap; use std::collections::HashMap;
use super::Connection; 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> { impl AsRef<dyn telepathy::ConnectionInterfaceRequests + 'static> for std::rc::Rc<Connection> {
fn as_ref(&self) -> &(dyn telepathy::ConnectionInterfaceRequests + 'static) { fn as_ref(&self) -> &(dyn telepathy::ConnectionInterfaceRequests + 'static) {
&**self &**self
} }
} }
type ChannelSpec = (dbus::Path<'static>, HashMap<String, VarArg>);
type RequestableChannelSpec = (HashMap<String, VarArg>, Vec<String>);
impl telepathy::ConnectionInterfaceRequests for Connection { impl telepathy::ConnectionInterfaceRequests for Connection {
fn create_channel( fn create_channel(
&self, &self,
request: HashMap<&str, VarArg>, request: HashMap<&str, VarArg>,
) -> Result<(dbus::Path<'static>, HashMap<String, VarArg>), MethodErr> { ) -> Result<(dbus::Path<'static>, HashMap<String, VarArg>)> {
println!("Connection<{}>::create_channel({:?})", self.id(), request); println!("Connection<{}>::create_channel({:?})", self.id(), request);
Err(MethodErr::no_arg()) // FIXME: should be NotImplemented? Err(MethodErr::no_arg()) // FIXME: should be NotImplemented?
@@ -29,17 +38,99 @@ impl telepathy::ConnectionInterfaceRequests for Connection {
fn ensure_channel( fn ensure_channel(
&self, &self,
request: HashMap<&str, VarArg>, request: HashMap<&str, VarArg>,
) -> Result<(bool, dbus::Path<'static>, HashMap<String, VarArg>), MethodErr> { ) -> Result<(bool, dbus::Path<'static>, HashMap<String, VarArg>)> {
let path = self.path().clone();
println!("Connection<{}>::ensure_channel({:?})", self.id(), request); println!("Connection<{}>::ensure_channel({:?})", self.id(), request);
Err(MethodErr::no_arg()) // FIXME: should be NotImplemented?
// 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>, MethodErr> { fn channels(&self) -> Result<Vec<ChannelSpec>> {
println!("Connection<{}>::channels()", self.id()); println!("Connection<{}>::channels()", self.id());
Ok(vec![])
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>, MethodErr> { fn requestable_channel_classes(&self) -> Result<Vec<RequestableChannelSpec>> {
println!("Connection<{}>::requestable_channel_classes()", self.id()); println!("Connection<{}>::requestable_channel_classes()", self.id());
Ok(requestables()) Ok(requestables())
} }

View File

@@ -14,7 +14,7 @@ pub const CM_OBJECT_PATH: &str = "/org/freedesktop/Telepathy/ConnectionManager/p
#[derive(Debug)] #[derive(Debug)]
pub struct ConnectionManager { pub struct ConnectionManager {
conns: Arc<Mutex<HashSet<String>>>, conns: Arc<Mutex<HashSet<dbus::Path<'static>>>>,
sender: mpsc::Sender<dbus::Message>, sender: mpsc::Sender<dbus::Message>,
} }
@@ -30,7 +30,7 @@ impl ConnectionManager {
( (
ConnectionManager { ConnectionManager {
conns: Arc::new(Mutex::new(HashSet::<String>::new())), conns: Arc::new(Mutex::new(HashSet::<dbus::Path<'static>>::new())),
sender: msg_s, sender: msg_s,
}, },
msg_r, msg_r,
@@ -46,26 +46,25 @@ impl ConnectionManager {
let mut conns = self.conns.lock().expect("Mutex access"); let mut conns = self.conns.lock().expect("Mutex access");
let dbus_conn_path = dbus::strings::Path::new(path.to_owned())
.expect("Object path should meet DBUS requirements");
// We can't call connect() multiple times on the connection yet // We can't call connect() multiple times on the connection yet
if conns.contains(&path) { if conns.contains(&path) {
return Err(MethodErr::no_arg()); return Err(MethodErr::no_arg());
} }
let conn = Connection::new(settings, self.conns.clone())?;
// It would be nice to have a single main loop, but thread-per-conn is // 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. // 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(); let (ok_s, ok_r) = mpsc::channel();
std::thread::spawn(move || conn.run(ok_s)); 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 // Emit a NewConnection signal for the benefit of others, but the caller
// learns from our RPC response // learns from our RPC response
let sig = telepathy::ConnectionManagerNewConnection { let sig = telepathy::ConnectionManagerNewConnection {
bus_name: bus.to_owned(), bus_name: bus.to_owned(),
object_path: dbus_conn_path.clone(), object_path: path.clone(),
protocol: super::PROTO_NAME.to_string(), protocol: super::PROTO_NAME.to_string(),
}; };
@@ -81,7 +80,7 @@ impl ConnectionManager {
return Err(MethodErr::no_arg()); return Err(MethodErr::no_arg());
} }
conns.insert(path); conns.insert(path.clone());
let dbus_cm_path = dbus::strings::Path::new(CM_OBJECT_PATH.to_string()) let dbus_cm_path = dbus::strings::Path::new(CM_OBJECT_PATH.to_string())
.expect("Object path should meet DBUS requirements"); .expect("Object path should meet DBUS requirements");
@@ -91,7 +90,7 @@ impl ConnectionManager {
.expect("send signal"); .expect("send signal");
// The bus name *must* be org.freedesktop.Telepathy.Connection.padfoot.delta.<name> // The bus name *must* be org.freedesktop.Telepathy.Connection.padfoot.delta.<name>
Ok((bus, dbus_conn_path)) Ok((bus, path))
} }
} }

138
src/padfoot/message.rs Normal file
View 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])
}

View File

@@ -1,4 +1,4 @@
use crate::padfoot::{var_str, var_u32, VarArg}; use crate::padfoot::{var_bool, var_str, var_u32, VarArg};
use crate::telepathy; use crate::telepathy;
use dbus::tree::MethodErr; use dbus::tree::MethodErr;
@@ -49,6 +49,7 @@ pub fn parameters() -> Vec<ParamSpec> {
"s".to_string(), "s".to_string(),
var_str("".to_string()), var_str("".to_string()),
), ),
("bcc-self".to_string(), 0, "b".to_string(), var_bool(false)),
] ]
} }

View File

@@ -1,5 +1,7 @@
use dbus::arg::{RefArg, Variant}; use dbus::arg::{RefArg, Variant};
use std::convert::TryInto;
pub type VarArg = Variant<Box<dyn RefArg + 'static>>; pub type VarArg = Variant<Box<dyn RefArg + 'static>>;
pub fn var_arg(item: Box<dyn RefArg + 'static>) -> VarArg { pub fn var_arg(item: Box<dyn RefArg + 'static>) -> VarArg {
@@ -14,6 +16,26 @@ pub fn var_str_vec(item: Vec<String>) -> VarArg {
var_arg(Box::new(item)) 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 { pub fn var_u32(item: u32) -> VarArg {
var_arg(Box::new(item)) 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()
}

View File

@@ -1,365 +1,485 @@
#![allow(unused)] #![allow(unused)]
#![allow(clippy::all)]
#[allow(clippy::all)]
mod account_interface_addressing; mod account_interface_addressing;
pub use self::account_interface_addressing::*; pub use self::account_interface_addressing::*;
#[allow(clippy::all)]
mod account_interface_avatar; mod account_interface_avatar;
pub use self::account_interface_avatar::*; pub use self::account_interface_avatar::*;
#[allow(clippy::all)]
mod account_interface_external_password_storage; mod account_interface_external_password_storage;
pub use self::account_interface_external_password_storage::*; pub use self::account_interface_external_password_storage::*;
#[allow(clippy::all)]
mod account_interface_hidden; mod account_interface_hidden;
pub use self::account_interface_hidden::*; pub use self::account_interface_hidden::*;
#[allow(clippy::all)]
mod account_interface_storage; mod account_interface_storage;
pub use self::account_interface_storage::*; pub use self::account_interface_storage::*;
#[allow(clippy::all)]
mod account_manager_interface_hidden; mod account_manager_interface_hidden;
pub use self::account_manager_interface_hidden::*; pub use self::account_manager_interface_hidden::*;
#[allow(clippy::all)]
mod account_manager; mod account_manager;
pub use self::account_manager::*; pub use self::account_manager::*;
#[allow(clippy::all)]
mod account; mod account;
pub use self::account::*; pub use self::account::*;
#[allow(clippy::all)]
mod all; mod all;
pub use self::all::*; pub use self::all::*;
#[allow(clippy::all)]
mod authentication_tls_certificate; mod authentication_tls_certificate;
pub use self::authentication_tls_certificate::*; pub use self::authentication_tls_certificate::*;
#[allow(clippy::all)]
mod call_content_interface_audio_control; mod call_content_interface_audio_control;
pub use self::call_content_interface_audio_control::*; pub use self::call_content_interface_audio_control::*;
#[allow(clippy::all)]
mod call_content_interface_dtmf; mod call_content_interface_dtmf;
pub use self::call_content_interface_dtmf::*; pub use self::call_content_interface_dtmf::*;
#[allow(clippy::all)]
mod call_content_interface_media; mod call_content_interface_media;
pub use self::call_content_interface_media::*; pub use self::call_content_interface_media::*;
#[allow(clippy::all)]
mod call_content_interface_video_control; mod call_content_interface_video_control;
pub use self::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; mod call_content_media_description_interface_rtcp_extended_reports;
pub use self::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; mod call_content_media_description_interface_rtcp_feedback;
pub use self::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; mod call_content_media_description_interface_rtp_header_extensions;
pub use self::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; mod call_content_media_description;
pub use self::call_content_media_description::*; pub use self::call_content_media_description::*;
#[allow(clippy::all)]
mod call_content; mod call_content;
pub use self::call_content::*; pub use self::call_content::*;
#[allow(clippy::all)]
mod call_interface_mute; mod call_interface_mute;
pub use self::call_interface_mute::*; pub use self::call_interface_mute::*;
#[allow(clippy::all)]
mod call_stream_endpoint; mod call_stream_endpoint;
pub use self::call_stream_endpoint::*; pub use self::call_stream_endpoint::*;
#[allow(clippy::all)]
mod call_stream_interface_media; mod call_stream_interface_media;
pub use self::call_stream_interface_media::*; pub use self::call_stream_interface_media::*;
#[allow(clippy::all)]
mod call_stream; mod call_stream;
pub use self::call_stream::*; pub use self::call_stream::*;
#[allow(clippy::all)]
mod channel_bundle; mod channel_bundle;
pub use self::channel_bundle::*; pub use self::channel_bundle::*;
#[allow(clippy::all)]
mod channel_dispatcher_interface_messages1; mod channel_dispatcher_interface_messages1;
pub use self::channel_dispatcher_interface_messages1::*; pub use self::channel_dispatcher_interface_messages1::*;
#[allow(clippy::all)]
mod channel_dispatcher_interface_operation_list; mod channel_dispatcher_interface_operation_list;
pub use self::channel_dispatcher_interface_operation_list::*; pub use self::channel_dispatcher_interface_operation_list::*;
#[allow(clippy::all)]
mod channel_dispatcher; mod channel_dispatcher;
pub use self::channel_dispatcher::*; pub use self::channel_dispatcher::*;
#[allow(clippy::all)]
mod channel_dispatch_operation; mod channel_dispatch_operation;
pub use self::channel_dispatch_operation::*; pub use self::channel_dispatch_operation::*;
#[allow(clippy::all)]
mod channel_future; mod channel_future;
pub use self::channel_future::*; pub use self::channel_future::*;
#[allow(clippy::all)]
mod channel_handler; mod channel_handler;
pub use self::channel_handler::*; pub use self::channel_handler::*;
#[allow(clippy::all)]
mod channel_interface_addressing; mod channel_interface_addressing;
pub use self::channel_interface_addressing::*; pub use self::channel_interface_addressing::*;
#[allow(clippy::all)]
mod channel_interface_anonymity; mod channel_interface_anonymity;
pub use self::channel_interface_anonymity::*; pub use self::channel_interface_anonymity::*;
#[allow(clippy::all)]
mod channel_interface_call_state; mod channel_interface_call_state;
pub use self::channel_interface_call_state::*; pub use self::channel_interface_call_state::*;
#[allow(clippy::all)]
mod channel_interface_captcha_authentication; mod channel_interface_captcha_authentication;
pub use self::channel_interface_captcha_authentication::*; pub use self::channel_interface_captcha_authentication::*;
#[allow(clippy::all)]
mod channel_interface_chat_state; mod channel_interface_chat_state;
pub use self::channel_interface_chat_state::*; pub use self::channel_interface_chat_state::*;
#[allow(clippy::all)]
mod channel_interface_conference; mod channel_interface_conference;
pub use self::channel_interface_conference::*; pub use self::channel_interface_conference::*;
#[allow(clippy::all)]
mod channel_interface_credentials_storage; mod channel_interface_credentials_storage;
pub use self::channel_interface_credentials_storage::*; pub use self::channel_interface_credentials_storage::*;
#[allow(clippy::all)]
mod channel_interface_destroyable; mod channel_interface_destroyable;
pub use self::channel_interface_destroyable::*; pub use self::channel_interface_destroyable::*;
#[allow(clippy::all)]
mod channel_interface_dtmf; mod channel_interface_dtmf;
pub use self::channel_interface_dtmf::*; pub use self::channel_interface_dtmf::*;
#[allow(clippy::all)]
mod channel_interface_file_transfer_metadata; mod channel_interface_file_transfer_metadata;
pub use self::channel_interface_file_transfer_metadata::*; pub use self::channel_interface_file_transfer_metadata::*;
#[allow(clippy::all)]
mod channel_interface_group; mod channel_interface_group;
pub use self::channel_interface_group::*; pub use self::channel_interface_group::*;
#[allow(clippy::all)]
mod channel_interface_hold; mod channel_interface_hold;
pub use self::channel_interface_hold::*; pub use self::channel_interface_hold::*;
#[allow(clippy::all)]
mod channel_interface_html; mod channel_interface_html;
pub use self::channel_interface_html::*; pub use self::channel_interface_html::*;
#[allow(clippy::all)]
mod channel_interface_media_signalling; mod channel_interface_media_signalling;
pub use self::channel_interface_media_signalling::*; pub use self::channel_interface_media_signalling::*;
#[allow(clippy::all)]
mod channel_interface_mergeable_conference; mod channel_interface_mergeable_conference;
pub use self::channel_interface_mergeable_conference::*; pub use self::channel_interface_mergeable_conference::*;
#[allow(clippy::all)]
mod channel_interface_messages; mod channel_interface_messages;
pub use self::channel_interface_messages::*; pub use self::channel_interface_messages::*;
#[allow(clippy::all)]
mod channel_interface_password; mod channel_interface_password;
pub use self::channel_interface_password::*; pub use self::channel_interface_password::*;
#[allow(clippy::all)]
mod channel_interface_picture; mod channel_interface_picture;
pub use self::channel_interface_picture::*; pub use self::channel_interface_picture::*;
#[allow(clippy::all)]
mod channel_interface_room_config; mod channel_interface_room_config;
pub use self::channel_interface_room_config::*; pub use self::channel_interface_room_config::*;
#[allow(clippy::all)]
mod channel_interface_room; mod channel_interface_room;
pub use self::channel_interface_room::*; pub use self::channel_interface_room::*;
#[allow(clippy::all)]
mod channel_interface_sasl_authentication; mod channel_interface_sasl_authentication;
pub use self::channel_interface_sasl_authentication::*; pub use self::channel_interface_sasl_authentication::*;
#[allow(clippy::all)]
mod channel_interface_securable; mod channel_interface_securable;
pub use self::channel_interface_securable::*; pub use self::channel_interface_securable::*;
#[allow(clippy::all)]
mod channel_interface_service_point; mod channel_interface_service_point;
pub use self::channel_interface_service_point::*; pub use self::channel_interface_service_point::*;
#[allow(clippy::all)]
mod channel_interface_sms; mod channel_interface_sms;
pub use self::channel_interface_sms::*; pub use self::channel_interface_sms::*;
#[allow(clippy::all)]
mod channel_interface_splittable; mod channel_interface_splittable;
pub use self::channel_interface_splittable::*; pub use self::channel_interface_splittable::*;
#[allow(clippy::all)]
mod channel_interface_subject; mod channel_interface_subject;
pub use self::channel_interface_subject::*; pub use self::channel_interface_subject::*;
#[allow(clippy::all)]
mod channel_interface_transfer; mod channel_interface_transfer;
pub use self::channel_interface_transfer::*; pub use self::channel_interface_transfer::*;
#[allow(clippy::all)]
mod channel_interface_tube; mod channel_interface_tube;
pub use self::channel_interface_tube::*; pub use self::channel_interface_tube::*;
#[allow(clippy::all)]
mod channel_request; mod channel_request;
pub use self::channel_request::*; pub use self::channel_request::*;
#[allow(clippy::all)]
mod channel_type_call; mod channel_type_call;
pub use self::channel_type_call::*; pub use self::channel_type_call::*;
#[allow(clippy::all)]
mod channel_type_contact_list; mod channel_type_contact_list;
pub use self::channel_type_contact_list::*; pub use self::channel_type_contact_list::*;
#[allow(clippy::all)]
mod channel_type_contact_search; mod channel_type_contact_search;
pub use self::channel_type_contact_search::*; pub use self::channel_type_contact_search::*;
#[allow(clippy::all)]
mod channel_type_dbus_tube; mod channel_type_dbus_tube;
pub use self::channel_type_dbus_tube::*; pub use self::channel_type_dbus_tube::*;
#[allow(clippy::all)]
mod channel_type_file_transfer; mod channel_type_file_transfer;
pub use self::channel_type_file_transfer::*; pub use self::channel_type_file_transfer::*;
#[allow(clippy::all)]
mod channel_type_room_list; mod channel_type_room_list;
pub use self::channel_type_room_list::*; pub use self::channel_type_room_list::*;
#[allow(clippy::all)]
mod channel_type_server_authentication; mod channel_type_server_authentication;
pub use self::channel_type_server_authentication::*; pub use self::channel_type_server_authentication::*;
#[allow(clippy::all)]
mod channel_type_server_tls_connection; mod channel_type_server_tls_connection;
pub use self::channel_type_server_tls_connection::*; pub use self::channel_type_server_tls_connection::*;
#[allow(clippy::all)]
mod channel_type_streamed_media; mod channel_type_streamed_media;
pub use self::channel_type_streamed_media::*; pub use self::channel_type_streamed_media::*;
#[allow(clippy::all)]
mod channel_type_stream_tube; mod channel_type_stream_tube;
pub use self::channel_type_stream_tube::*; pub use self::channel_type_stream_tube::*;
#[allow(clippy::all)]
mod channel_type_text; mod channel_type_text;
pub use self::channel_type_text::*; pub use self::channel_type_text::*;
#[allow(clippy::all)]
mod channel_type_tubes; mod channel_type_tubes;
pub use self::channel_type_tubes::*; pub use self::channel_type_tubes::*;
#[allow(clippy::all)]
mod channel; mod channel;
pub use self::channel::*; pub use self::channel::*;
#[allow(clippy::all)]
mod client_approver; mod client_approver;
pub use self::client_approver::*; pub use self::client_approver::*;
#[allow(clippy::all)]
mod client_handler_future; mod client_handler_future;
pub use self::client_handler_future::*; pub use self::client_handler_future::*;
#[allow(clippy::all)]
mod client_handler; mod client_handler;
pub use self::client_handler::*; pub use self::client_handler::*;
#[allow(clippy::all)]
mod client_interface_requests; mod client_interface_requests;
pub use self::client_interface_requests::*; pub use self::client_interface_requests::*;
#[allow(clippy::all)]
mod client_observer; mod client_observer;
pub use self::client_observer::*; pub use self::client_observer::*;
#[allow(clippy::all)]
mod client; mod client;
pub use self::client::*; pub use self::client::*;
#[allow(clippy::all)]
mod connection_interface_addressing; mod connection_interface_addressing;
pub use self::connection_interface_addressing::*; pub use self::connection_interface_addressing::*;
#[allow(clippy::all)]
mod connection_interface_aliasing; mod connection_interface_aliasing;
pub use self::connection_interface_aliasing::*; pub use self::connection_interface_aliasing::*;
#[allow(clippy::all)]
mod connection_interface_anonymity; mod connection_interface_anonymity;
pub use self::connection_interface_anonymity::*; pub use self::connection_interface_anonymity::*;
#[allow(clippy::all)]
mod connection_interface_avatars; mod connection_interface_avatars;
pub use self::connection_interface_avatars::*; pub use self::connection_interface_avatars::*;
#[allow(clippy::all)]
mod connection_interface_balance; mod connection_interface_balance;
pub use self::connection_interface_balance::*; pub use self::connection_interface_balance::*;
#[allow(clippy::all)]
mod connection_interface_capabilities; mod connection_interface_capabilities;
pub use self::connection_interface_capabilities::*; pub use self::connection_interface_capabilities::*;
#[allow(clippy::all)]
mod connection_interface_cellular; mod connection_interface_cellular;
pub use self::connection_interface_cellular::*; pub use self::connection_interface_cellular::*;
#[allow(clippy::all)]
mod connection_interface_client_types; mod connection_interface_client_types;
pub use self::connection_interface_client_types::*; pub use self::connection_interface_client_types::*;
#[allow(clippy::all)]
mod connection_interface_communication_policy; mod connection_interface_communication_policy;
pub use self::connection_interface_communication_policy::*; pub use self::connection_interface_communication_policy::*;
#[allow(clippy::all)]
mod connection_interface_contact_blocking; mod connection_interface_contact_blocking;
pub use self::connection_interface_contact_blocking::*; pub use self::connection_interface_contact_blocking::*;
#[allow(clippy::all)]
mod connection_interface_contact_capabilities; mod connection_interface_contact_capabilities;
pub use self::connection_interface_contact_capabilities::*; pub use self::connection_interface_contact_capabilities::*;
#[allow(clippy::all)]
mod connection_interface_contact_groups; mod connection_interface_contact_groups;
pub use self::connection_interface_contact_groups::*; pub use self::connection_interface_contact_groups::*;
#[allow(clippy::all)]
mod connection_interface_contact_info; mod connection_interface_contact_info;
pub use self::connection_interface_contact_info::*; pub use self::connection_interface_contact_info::*;
#[allow(clippy::all)]
mod connection_interface_contact_list; mod connection_interface_contact_list;
pub use self::connection_interface_contact_list::*; pub use self::connection_interface_contact_list::*;
#[allow(clippy::all)]
mod connection_interface_contacts; mod connection_interface_contacts;
pub use self::connection_interface_contacts::*; pub use self::connection_interface_contacts::*;
#[allow(clippy::all)]
mod connection_interface_forwarding; mod connection_interface_forwarding;
pub use self::connection_interface_forwarding::*; pub use self::connection_interface_forwarding::*;
#[allow(clippy::all)]
mod connection_interface_irc_command1; mod connection_interface_irc_command1;
pub use self::connection_interface_irc_command1::*; pub use self::connection_interface_irc_command1::*;
#[allow(clippy::all)]
mod connection_interface_keepalive; mod connection_interface_keepalive;
pub use self::connection_interface_keepalive::*; pub use self::connection_interface_keepalive::*;
#[allow(clippy::all)]
mod connection_interface_location; mod connection_interface_location;
pub use self::connection_interface_location::*; pub use self::connection_interface_location::*;
#[allow(clippy::all)]
mod connection_interface_mail_notification; mod connection_interface_mail_notification;
pub use self::connection_interface_mail_notification::*; pub use self::connection_interface_mail_notification::*;
#[allow(clippy::all)]
mod connection_interface_power_saving; mod connection_interface_power_saving;
pub use self::connection_interface_power_saving::*; pub use self::connection_interface_power_saving::*;
#[allow(clippy::all)]
mod connection_interface_presence; mod connection_interface_presence;
pub use self::connection_interface_presence::*; pub use self::connection_interface_presence::*;
#[allow(clippy::all)]
mod connection_interface_privacy; mod connection_interface_privacy;
pub use self::connection_interface_privacy::*; pub use self::connection_interface_privacy::*;
#[allow(clippy::all)]
mod connection_interface_renaming; mod connection_interface_renaming;
pub use self::connection_interface_renaming::*; pub use self::connection_interface_renaming::*;
#[allow(clippy::all)]
mod connection_interface_requests; mod connection_interface_requests;
pub use self::connection_interface_requests::*; pub use self::connection_interface_requests::*;
#[allow(clippy::all)]
mod connection_interface_resources; mod connection_interface_resources;
pub use self::connection_interface_resources::*; pub use self::connection_interface_resources::*;
#[allow(clippy::all)]
mod connection_interface_service_point; mod connection_interface_service_point;
pub use self::connection_interface_service_point::*; pub use self::connection_interface_service_point::*;
#[allow(clippy::all)]
mod connection_interface_sidecars1; mod connection_interface_sidecars1;
pub use self::connection_interface_sidecars1::*; pub use self::connection_interface_sidecars1::*;
#[allow(clippy::all)]
mod connection_interface_simple_presence; mod connection_interface_simple_presence;
pub use self::connection_interface_simple_presence::*; pub use self::connection_interface_simple_presence::*;
#[allow(clippy::all)]
mod connection_manager_interface_account_storage; mod connection_manager_interface_account_storage;
pub use self::connection_manager_interface_account_storage::*; pub use self::connection_manager_interface_account_storage::*;
#[allow(clippy::all)]
mod connection_manager; mod connection_manager;
pub use self::connection_manager::*; pub use self::connection_manager::*;
#[allow(clippy::all)]
mod connection; mod connection;
pub use self::connection::*; pub use self::connection::*;
#[allow(clippy::all)]
mod debug; mod debug;
pub use self::debug::*; pub use self::debug::*;
#[allow(clippy::all)]
mod errors; mod errors;
pub use self::errors::*; pub use self::errors::*;
#[allow(clippy::all)]
mod generic_types; mod generic_types;
pub use self::generic_types::*; pub use self::generic_types::*;
#[allow(clippy::all)]
mod media_session_handler; mod media_session_handler;
pub use self::media_session_handler::*; pub use self::media_session_handler::*;
#[allow(clippy::all)]
mod media_stream_handler; mod media_stream_handler;
pub use self::media_stream_handler::*; pub use self::media_stream_handler::*;
#[allow(clippy::all)]
mod properties_interface; mod properties_interface;
pub use self::properties_interface::*; pub use self::properties_interface::*;
#[allow(clippy::all)]
mod protocol_interface_addressing; mod protocol_interface_addressing;
pub use self::protocol_interface_addressing::*; pub use self::protocol_interface_addressing::*;
#[allow(clippy::all)]
mod protocol_interface_avatars; mod protocol_interface_avatars;
pub use self::protocol_interface_avatars::*; pub use self::protocol_interface_avatars::*;
#[allow(clippy::all)]
mod protocol_interface_presence; mod protocol_interface_presence;
pub use self::protocol_interface_presence::*; pub use self::protocol_interface_presence::*;
#[allow(clippy::all)]
mod protocol; mod protocol;
pub use self::protocol::*; pub use self::protocol::*;
#[allow(clippy::all)]
mod template; mod template;
pub use self::template::*; pub use self::template::*;

View 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