Compare commits

...

16 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
19 changed files with 10454 additions and 755 deletions

812
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -9,8 +9,9 @@ license = "MIT"
[dependencies]
anyhow = "1.0"
async-std = "1.6"
dbus = "0.8"
deltachat = { git = "https://github.com/deltachat/deltachat-core-rust", tag="1.33.0" }
deltachat = { git = "https://github.com/deltachat/deltachat-core-rust", tag="1.34.0" }
directories = "2.0"
rand = "0.7"

View File

@@ -33,10 +33,10 @@ Here's where we're at right now:
- [x] Appear as online in Empathy
- [x] Disconnect!
- [ ] Set up an account manually
- [ ] Contacts handling
- [~] Contacts handling
- [x] Text messages
- [ ] Multimedia messages
- [ ] Setup messages
- [~] Setup messages
- [ ] Import/Export
- [ ] Group chats
- [ ] Geolocation messages
@@ -95,6 +95,63 @@ This creates a 32-bit executable at `target/i686-unknown-linux-gnu/release/telep
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.
@@ -103,12 +160,36 @@ the binary into `/usr/lib/telepathy/telepathy-padfoot`.
I should probably put this into the makefile.
### Running
D-Bus activation is not enabled yet, since it gets in the way of disaster-driven
development. Just run the `telepathy-padfoot` binary as the same user that your
chat client will be running as. It registers to the DBUS **session bus**, and
will be picked up next time your chat client scans (which may need a restart).
### Setup messages
If you send an autocrypt setup message while a padfoot connection is up, it will
notice it and open a channel asking you to reply with a message like:
```
IMEX: <id> nnnn-nnnn-nnnn-nnnn-nnnn-nnnn-nnnn-nnnn-nnnn
```
The ID is the delta-generated message ID, while the rest is the setup code. No
whitespace!
This bit is still extremely janky; it should instead be a magic channel type or
action button of some kind. It is, however, functional.
Delta wants us to enable the "Send copy to self" option in settings. That's
exposed as "Bcc self" in the advanced options in Empathy. Once enabled, messages
you send via Padfoot will appear in other clients. However, messages you send
from other clients don't appear in Padfoot yet because it mostly ignores self
messages as a dirty hack to avoid double-showing messages. Progess though.
### Autogenerated telepathy DBUS bindings
It makes use of the `dbus-codegen-rust` crate to convert the
[telepathy interface specs](https://github.com/TelepathyIM/telepathy-spec) into
the executable code in `src/telepathy`. This is checked in, but can be
@@ -123,4 +204,4 @@ $ ./scripts/dbus-codegen
`dbus-codegen-rust` doesn't seem to handle namespaced attributes properly, so
we modify the XML files in `telepathy-spec`... with `sed`. The `tp:type`
attribute is renamed to `tp:typehint`.
attribute is renamed to `tp:typehint`. This will be fixed in the next release.

70
about.hbs Normal file
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

View File

@@ -5,14 +5,14 @@ BusName=org.freedesktop.Telepathy.ConnectionManager.padfoot
ObjectPath=/org/freedesktop/Telepathy/ConnectionManager/padfoot
[Protocol delta]
Interfaces=org.freedesktop.Telepathy.Protocol.Interface.Presence;
param-account=s required
param-password=s required secret
param-bcc-self=b
status-available=2 settable
status-offline = 1 settable
AuthenticationTypes=org.freedesktop.Telepathy.Channel.Type.ServerTLSConnection;
ConnectionInterfaces=org.freedesktop.Telepathy.Connection.Interface.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
Icon=im-delta
Interfaces=org.freedesktop.Telepathy.Protocol;org.freedesktop.Telepathy.Protocol.Interface.Presence;

View File

@@ -11,10 +11,11 @@ pub use type_text::*;
use crate::padfoot::{var_bool, var_str, var_str_vec, var_u32, DbusAction, VarArg};
use crate::telepathy;
use async_std::task::block_on;
use deltachat as dc;
use std::collections::HashMap;
use std::sync::{mpsc, Arc, RwLock};
use std::sync::{mpsc, Arc};
type Result<T> = std::result::Result<T, dbus::tree::MethodErr>;
@@ -35,7 +36,7 @@ pub const HANDLE_TYPE_GROUP: HandleType = 4; // Deprecated
pub struct Channel {
actq: mpsc::Sender<DbusAction>,
chat_id: dc::chat::ChatId,
ctx: Arc<RwLock<dc::context::Context>>,
ctx: Arc<dc::context::Context>,
initiator_handle: u32,
path: dbus::Path<'static>,
requested: bool,
@@ -51,7 +52,7 @@ impl Channel {
pub fn new(
actq: mpsc::Sender<DbusAction>,
chat_id: dc::chat::ChatId,
ctx: Arc<RwLock<dc::context::Context>>,
ctx: Arc<dc::context::Context>,
initiator_handle: u32,
path: dbus::Path<'static>,
requested: bool,
@@ -79,6 +80,14 @@ impl Channel {
"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),
@@ -118,15 +127,15 @@ impl Channel {
}
pub fn target_contact(&self) -> Option<dc::contact::Contact> {
let ctx = self.ctx.read().unwrap();
dc::contact::Contact::get_by_id(&ctx, self.handle()).ok()
block_on(dc::contact::Contact::get_by_id(&self.ctx, self.handle())).ok()
}
pub fn initiator_contact(&self) -> Option<dc::contact::Contact> {
let ctx = self.ctx.read().unwrap();
dc::contact::Contact::get_by_id(&ctx, self.initiator_handle).ok() // FIXME: this will be wrong for outbound channels
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 {
@@ -154,4 +163,35 @@ impl Channel {
.add(messages_iface)
.add(type_text_iface)
}
fn try_process_setupmsg(self: &Self, text: String) {
if !text.starts_with("IMEX: ") {
return;
};
// Expected form: "IMEX: <msg-id> <setupcode>
let mut iter = text.split_whitespace();
iter.next(); // Ignore the prefix
let msg_id = match iter.next() {
Some(txt) => match txt.parse::<u32>() {
Ok(id) => id,
_ => return,
},
_ => return,
};
let setup_code = match iter.next() {
Some(txt) => txt,
_ => return,
};
if let Err(e) = block_on(dc::imex::continue_key_transfer(
&self.ctx,
dc::message::MsgId::new(msg_id),
&setup_code,
)) {
println!("Failed to apply setup code {}: {}", msg_id, e);
}
}
}

View File

@@ -1,6 +1,7 @@
use crate::padfoot::{convert_msg, DbusAction, VarArg};
use crate::telepathy;
use async_std::task::block_on;
use dbus::message::SignalArgs;
use dbus::tree::MethodErr;
use dc::constants::Viewtype;
@@ -34,11 +35,18 @@ impl telepathy::ChannelInterfaceMessages for Channel {
}
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());
let ctx = self.ctx.read().unwrap();
let msg_id = match dc::chat::send_msg(&ctx, self.chat_id, &mut delta_msg) {
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);
@@ -47,7 +55,7 @@ impl telepathy::ChannelInterfaceMessages for Channel {
};
let token = format!("{}", msg_id.to_u32());
let dbus_parts = convert_msg(&delta_msg);
let dbus_parts = convert_msg(blobdir, &delta_msg).map_err(|_| MethodErr::no_arg())?;
let messages_sig = telepathy::ChannelInterfaceMessagesMessageSent {
content: dbus_parts,
@@ -84,7 +92,7 @@ impl telepathy::ChannelInterfaceMessages for Channel {
fn supported_content_types(&self) -> Result<Vec<String>> {
println!("Channel::supported_content_types()");
Ok(vec!["text/plain".to_string()]) // TODO: image support
Ok(vec!["*/*".to_string()])
}
fn message_types(&self) -> Result<Vec<u32>> {
@@ -104,13 +112,17 @@ impl telepathy::ChannelInterfaceMessages for Channel {
println!("Channel::pending_messages()");
let mut out = Vec::<Vec<HashMap<String, VarArg>>>::new();
let ctx = self.ctx.read().unwrap();
let ctx = &self.ctx;
let blobdir = ctx.get_blobdir();
for msg_id in dc::chat::get_chat_msgs(&ctx, self.chat_id, 0, None) {
if let Ok(msg) = dc::message::Message::load_from_db(&ctx, msg_id) {
println!(" A message: {:?}", msg);
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 => out.push(convert_msg(&msg)),
MessageState::InFresh | MessageState::InNoticed => {
println!(" A message: {:?}", msg);
let parts = convert_msg(blobdir, &msg).map_err(|_| MethodErr::no_arg())?;
out.push(parts);
}
_ => continue,
}
}

View File

@@ -2,6 +2,7 @@ use crate::padfoot::DbusAction;
use crate::telepathy;
use crate::telepathy::ChannelInterfaceMessages;
use async_std::task::block_on;
use dbus::message::SignalArgs;
use dbus::tree::MethodErr;
use dc::message::MsgId;
@@ -31,14 +32,13 @@ impl telepathy::ChannelTypeText for Channel {
fn acknowledge_pending_messages(&self, ids: Vec<u32>) -> Result<()> {
println!("Channel::acknowledge_pending_messages({:?})", ids);
let ctx = self.ctx.read().unwrap();
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 = dc::message::markseen_msgs(&ctx, &msg_ids);
let result = block_on(dc::message::markseen_msgs(&self.ctx, msg_ids));
if result {
println!("OK!");

View File

@@ -23,6 +23,7 @@ pub use self::simple_presence::*;
use crate::padfoot::{convert_msg, Channel, VarArg};
use crate::telepathy;
use async_std::task::block_on;
use dbus::blocking::{stdintf::org_freedesktop_dbus::RequestNameReply, LocalConnection};
use dbus::channel::{MatchingReceiver, Sender};
use dbus::message::SignalArgs;
@@ -35,6 +36,7 @@ use deltachat as dc;
use std::collections::{HashMap, HashSet};
use std::sync::{mpsc, Arc, Mutex, RwLock};
use std::thread;
use std::time::Duration;
pub const CONN_BUS_NAME: &str = "org.freedesktop.Telepathy.Connection.padfoot.delta";
@@ -66,7 +68,7 @@ pub struct Connection {
// Owned by the CM. Remove ourselves from this when done
conns: Arc<Mutex<HashSet<dbus::Path<'static>>>>,
ctx: Arc<RwLock<Context>>,
ctx: Arc<Context>, // Delta contexts are threadsafe
settings: ConnSettings,
state: Arc<RwLock<ConnState>>,
}
@@ -75,6 +77,7 @@ pub struct Connection {
pub struct ConnSettings {
account: String,
password: String,
bcc_self: bool,
id: String,
}
@@ -100,9 +103,24 @@ impl ConnSettings {
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 {
account,
password,
bcc_self,
id,
})
}
@@ -125,9 +143,11 @@ impl Connection {
settings: ConnSettings,
conns: Arc<Mutex<HashSet<dbus::Path<'static>>>>,
) -> Result<(Self, mpsc::Receiver<DbusAction>), MethodErr> {
let mut dbfile = directories::ProjectDirs::from("gs", "ur", "telepathy-padfoot")
.ok_or_else(MethodErr::no_arg)
.and_then(|p| Ok(p.data_local_dir().to_path_buf()))?;
let proj_dir = directories::ProjectDirs::from("gs", "ur", "telepathy-padfoot")
.ok_or_else(MethodErr::no_arg)?;
let mut dbfile = async_std::path::PathBuf::new();
dbfile.push(proj_dir.data_local_dir().to_str().unwrap());
dbfile.push(settings.id());
dbfile.push("db.sqlite3");
@@ -135,98 +155,104 @@ impl Connection {
let (q_s, q_r) = mpsc::channel::<DbusAction>();
let id = settings.id();
// The closure is shared between several different threads in delta, and
// we can't Send *or* clone the mpsc sender across them, so just wrap it
// in a mutex for now
let queue = Mutex::new(q_s.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);
}
Event::MsgsChanged { chat_id, msg_id } => {
println!(
"Connection<{}>: Messages changed for {}: {}",
id, chat_id, msg_id
);
queue
.lock()
.unwrap()
.send(DbusAction::IncomingMessage(chat_id, msg_id))
.unwrap();
}
Event::IncomingMsg { chat_id, msg_id } => {
println!(
"Connection<{}>: Incoming message for {}: {}",
id, chat_id, msg_id
);
queue
.lock()
.unwrap()
.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),
};
};
let ctx =
Context::new(Box::new(f), "telepathy-padfoot".to_string(), dbfile).map_err(|e| {
let ctx = Arc::new(
block_on(Context::new("telepathy-padfoot".to_string(), dbfile)).map_err(|e| {
println!(
"Connection<{}>::new(): couldn't get delta context: {}",
settings.id(),
e
);
MethodErr::no_arg() // FIXME: better error handling
})?;
})?,
);
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())?;
ctx.set_config(Config::MailPw, Some(&settings.password))
.map_err(|_e| MethodErr::no_arg())?;
ctx.set_config(Config::SentboxWatch, Some(&"Sent"))
block_on(ctx.set_config(Config::MailPw, Some(&settings.password)))
.map_err(|_e| MethodErr::no_arg())?;
if !ctx.is_configured() {
ctx.configure();
if settings.bcc_self {
block_on(ctx.set_config(Config::BccSelf, Some(&"1")))
.map_err(|_e| MethodErr::no_arg())?;
} else {
block_on(ctx.set_config(Config::BccSelf, Some(&"0")))
.map_err(|_e| MethodErr::no_arg())?;
}
if !block_on(ctx.is_configured()) {
block_on(ctx.configure()).unwrap();
};
Ok((
Connection {
conns,
settings,
actq: q_s,
channels: Arc::new(RwLock::new(
HashMap::<dbus::Path<'static>, Arc<Channel>>::new(),
)),
ctx: Arc::new(RwLock::new(ctx)),
conns,
ctx,
settings,
state: Arc::new(RwLock::new(ConnState::Initial)),
},
q_r,
@@ -377,73 +403,86 @@ impl Connection {
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!("Message {} received for {}...", msg_id, chan_path);
// Emit new message signals for the channel
if let Ok(msg) = dc::message::Message::load_from_db(
&ctx.clone().read().unwrap(),
msg_id,
) {
// Ignore messages that are self-originated.
// FIXME: special-case self-chats
if msg.get_from_id() == dc::constants::DC_CONTACT_ID_SELF {
println!("from ourselves, skipping");
continue;
}
let parts = convert_msg(&msg);
let sig = telepathy::ChannelInterfaceMessagesMessageReceived {
message: parts,
}
.to_emit_message(&chan_path);
actq.send(DbusAction::Signal(sig)).unwrap();
println!("OK!");
} else {
println!(" couldn't find message, not sending signal");
}
// FIXME: We MUST also send a Text.Received signal
} else {
if !chans.contains_key(&chan_path) {
print!("Channel for {} doesn't exist yet, creating it...", chat_id);
let contacts =
dc::chat::get_chat_contacts(&ctx.clone().read().unwrap(), chat_id);
if contacts.len() != 1 {
let contacts = block_on(dc::chat::get_chat_contacts(&ctx, chat_id));
if contacts.len() > 1 {
println!("...{} contacts in chat, ignoring!", contacts.len());
continue;
}
let handle = contacts.first().unwrap();
// 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
*handle, // initiator is the remote contact
chan_path,
false, // FIXME: this needs to handle requested channels
dc::constants::DC_CONTACT_ID_SELF, // target is us
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_rc = ctx.clone();
let ctx = ctx_rc.read().unwrap();
let ctx = ctx.clone();
for msg_id in dc::context::Context::get_fresh_msgs(&ctx) {
for msg_id in block_on(dc::context::Context::get_fresh_msgs(&ctx)) {
println!(" FRESH MESSAGE: {}", msg_id);
match dc::message::Message::load_from_db(&ctx, msg_id) {
match block_on(dc::message::Message::load_from_db(&ctx, msg_id)) {
Ok(msg) => {
actq.send(DbusAction::IncomingMessage(
msg.get_chat_id(),

View File

@@ -1,6 +1,7 @@
use crate::telepathy;
use crate::telepathy::{ConnectionInterfaceContacts, ConnectionInterfaceRequests}; // Non-deprecated channel methods
use async_std::task::block_on;
use dbus::message::SignalArgs;
use dbus::tree::MethodErr;
use dc::contact::Contact;
@@ -57,64 +58,12 @@ impl telepathy::Connection for Connection {
fn connect(&self) -> Result<()> {
println!("Connection<{}>::connect()", self.id());
let inbox_ctx = self.ctx.clone();
let state = self.state.clone();
let id = self.id();
let _inbox_thread = thread::spawn(move || {
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());
let io_ctx = self.ctx.clone();
let io_id = self.id();
let _io_thread = thread::spawn(move || {
block_on(io_ctx.start_io());
if *state.read().unwrap() != ConnState::Disconnected {
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);
println!("Connection<{}>::connect(): I/O thread exited", io_id);
});
// Just pretend to be connected all the time for now. Tracking IMAP+SMTP
@@ -122,7 +71,7 @@ impl telepathy::Connection for Connection {
let state = self.state.clone();
let mut w = state.write().unwrap();
*w = ConnState::Connected;
let ctx = self.ctx.read().unwrap();
let ctx = self.ctx.clone();
// Emit a StatusChanged signal for the benefit of others, but the caller
// learns from our RPC response
@@ -136,13 +85,13 @@ impl telepathy::Connection for Connection {
self.actq.send(DbusAction::FreshMessages).unwrap();
// If we can, emit signals on connect about the contact list
if let Ok(handles) = Contact::get_all(
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 {
@@ -170,17 +119,12 @@ impl telepathy::Connection for Connection {
fn disconnect(&self) -> Result<()> {
println!("Connection<{}>::disconnect()", self.id());
let ctx = self.ctx.read().unwrap();
block_on(self.ctx.stop_io());
let state = self.state.clone();
let mut w = state.write().unwrap();
*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
// connection from the active list
@@ -330,7 +274,7 @@ impl telepathy::Connection for Connection {
match handle_type {
crate::padfoot::HANDLE_TYPE_CONTACT => {
let ctx = self.ctx.read().unwrap();
let ctx = &self.ctx;
let mut out = Vec::<u32>::new();
// Identifiers is a list of email addresses. These can be
@@ -340,12 +284,16 @@ impl telepathy::Connection for Connection {
// FIXME: will it be faster to get all and filter?
for addr in identifiers {
let id = Contact::lookup_id_by_addr(&ctx, addr, dc::contact::Origin::Unknown);
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 Contact::create(&ctx, &addr, &addr) {
match block_on(Contact::create(ctx, &addr, &addr)) {
Ok(new_id) => out.push(new_id),
Err(e) => {
println!("Failed to add contact {}: {}", addr, e);
@@ -384,10 +332,10 @@ impl telepathy::Connection for Connection {
fn self_id(&self) -> Result<String> {
println!("Connection<{}>::self_id()", self.id());
let contact = match dc::contact::Contact::get_by_id(
&self.ctx.read().unwrap(),
let contact = match block_on(dc::contact::Contact::get_by_id(
&self.ctx,
dc::constants::DC_CONTACT_ID_SELF,
) {
)) {
Ok(c) => c,
Err(e) => {
println!(" err: {}", e);

View File

@@ -1,9 +1,11 @@
use crate::padfoot::VarArg;
use crate::telepathy;
use async_std::task::block_on;
use dbus::tree::MethodErr;
use deltachat::constants::DC_GCL_ADD_SELF;
use deltachat::contact::Contact;
use dc::constants::DC_GCL_ADD_SELF;
use dc::contact::Contact;
use deltachat as dc;
use std::collections::HashMap;
use std::convert::TryInto;
use telepathy::ConnectionInterfaceContacts; // for get_contact_attributes
@@ -30,8 +32,11 @@ impl telepathy::ConnectionInterfaceContactList for Connection {
hold
);
let ctx = &self.ctx.read().unwrap();
let ids = match Contact::get_all(ctx, DC_GCL_ADD_SELF.try_into().unwrap(), None::<String>) {
let ids = match block_on(Contact::get_all(
&self.ctx,
DC_GCL_ADD_SELF.try_into().unwrap(),
None::<String>,
)) {
Ok(c) => c,
Err(e) => {
println!(" err: {}", e);
@@ -63,6 +68,17 @@ impl telepathy::ConnectionInterfaceContactList for Connection {
}
fn remove_contacts(&self, contacts: Vec<u32>) -> Result<(), MethodErr> {
println!("Connection<{}>::remove_contacts({:?})", self.id(), contacts);
for contact_id in contacts {
// FIXME: don't ignore errors
if let Err(e) = block_on(Contact::delete(&self.ctx, contact_id)) {
println!(" Deleting contact {} failed: {}", contact_id, e);
}
}
// FIXME: signals MUST be emitted before this method returns
// FIXME: emitting signals at all would be great
Ok(())
}
fn unsubscribe(&self, contacts: Vec<u32>) -> Result<(), MethodErr> {

View File

@@ -1,6 +1,7 @@
use crate::padfoot::{var_str, var_u32, VarArg};
use crate::telepathy;
use async_std::task::block_on;
use dbus::tree::MethodErr;
use deltachat::contact::{Contact, Origin};
use std::collections::HashMap;
@@ -31,7 +32,7 @@ impl telepathy::ConnectionInterfaceContacts for Connection {
let mut out = HashMap::<u32, HashMap<String, VarArg>>::new();
for id in handles.iter() {
// FIXME: work out how to use get_all
let contact = match Contact::get_by_id(&self.ctx.read().unwrap(), *id) {
let contact = match block_on(Contact::get_by_id(&self.ctx, *id)) {
Ok(c) => c,
Err(_e) => continue, // Invalid IDs are silently ignored
};
@@ -77,10 +78,11 @@ impl telepathy::ConnectionInterfaceContacts for Connection {
interfaces
);
let id = {
let ctx = &self.ctx.read().unwrap();
Contact::lookup_id_by_addr(ctx, identifier, Origin::Unknown)
};
let id = block_on(Contact::lookup_id_by_addr(
&self.ctx,
identifier,
Origin::Unknown,
));
if id == 0 {
return Err(MethodErr::no_arg()); // FIXME: should be InvalidHandle

View File

@@ -1,6 +1,7 @@
use crate::padfoot::{get_var_str, get_var_u32, requestables, Channel, DbusAction, VarArg};
use crate::telepathy;
use async_std::task::block_on;
use dbus::tree::MethodErr;
use dc::contact::Contact;
use deltachat as dc;
@@ -77,10 +78,11 @@ impl telepathy::ConnectionInterfaceRequests for Connection {
return Err(MethodErr::no_arg());
};
let ctx = self.ctx.read().unwrap();
let target_handle =
Contact::lookup_id_by_addr(&ctx, target_id.clone(), dc::contact::Origin::Unknown);
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());
@@ -96,12 +98,12 @@ impl telepathy::ConnectionInterfaceRequests for Connection {
}
// Now we need to discover or create a chat id for the contact
let chat_id = dc::chat::create_by_contact_id(&ctx, target_handle).unwrap();
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
dc::constants::DC_CONTACT_ID_SELF, // initiator is self in this case
channel_path.clone(),
true, // requested
target_handle,

View File

@@ -1,37 +1,71 @@
use crate::padfoot::{var_i64, var_str, var_u32, VarArg};
use crate::padfoot::{var_bytearray, var_i64, var_str, var_u32, VarArg};
use deltachat::message::Message;
use async_std::path::{Path, PathBuf};
use dc::constants::Viewtype as Vt;
use dc::message::Message;
use deltachat as dc;
use std::collections::HashMap;
// Turns a deltachat::message::Message into a Telepathy Message_Part_List
pub fn convert_msg(msg: &Message) -> Vec<HashMap<String, VarArg>> {
let mut parts = Vec::new();
let mut props = HashMap::new();
let msg_id = msg.get_id();
type Part = HashMap<String, VarArg>;
type Result<T> = std::result::Result<T, Box<dyn std::error::Error>>;
props.insert(
// 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())),
);
props.insert("message-sent".to_string(), var_i64(msg.get_timestamp()));
props.insert(
out.insert("message-sent".to_string(), var_i64(msg.get_timestamp()));
out.insert(
"message-received".to_string(),
var_i64(msg.get_received_timestamp()),
);
props.insert("message-sender".to_string(), var_u32(msg.get_from_id()));
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?
props.insert("message-type".to_string(), var_u32(0)); // normal
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());
props.insert("pending-message-id".to_string(), var_u32(msg_id.to_u32()));
parts.push(props);
out.insert("pending-message-id".to_string(), var_u32(msg_id.to_u32()));
// Don't need these
// props.insert("interface", var_str());
@@ -39,17 +73,66 @@ pub fn convert_msg(msg: &Message) -> Vec<HashMap<String, VarArg>> {
// props.insert("silent", var_bool());
// props.insert("rescued", var_bool());
if let Some(text) = msg.get_text() {
let mut part = HashMap::new();
part.insert(
"content-type".to_string(),
var_str("text/plain".to_string()),
);
part.insert("content".to_string(), var_str(text));
parts.push(part);
}
// TODO: other parts. Can a deltachat message have multiple parts?
parts
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 dbus::tree::MethodErr;
@@ -49,6 +49,7 @@ pub fn parameters() -> Vec<ParamSpec> {
"s".to_string(),
var_str("".to_string()),
),
("bcc-self".to_string(), 0, "b".to_string(), var_bool(false)),
]
}

View File

@@ -20,6 +20,10 @@ pub fn var_bool(item: bool) -> VarArg {
var_arg(Box::new(item))
}
pub fn var_bytearray(item: std::vec::Vec<u8>) -> VarArg {
var_arg(Box::new(item))
}
pub fn var_u32(item: u32) -> VarArg {
var_arg(Box::new(item))
}

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