Files
telepathy-padfoot/src/padfoot/connection.rs

393 lines
13 KiB
Rust

use crate::telepathy;
use dbus::blocking::{stdintf::org_freedesktop_dbus::RequestNameReply, LocalConnection};
use dbus::tree;
use dc::config::Config;
use dc::context::Context;
use dc::Event;
use deltachat as dc;
use std::collections::HashMap;
use std::sync::{Arc, RwLock};
use std::thread;
use std::time::Duration;
pub const CONN_BUS_NAME: &'static str = "org.freedesktop.Telepathy.Connection.padfoot.delta";
pub const CONN_OBJECT_PATH: &'static str = "/org/freedesktop/Telepathy/Connection/padfoot/delta";
#[derive(Debug)]
pub struct Connection {
id: String,
ctx: Arc<RwLock<Context>>,
}
fn escape_one(b: u8) -> String {
format!("_{:0<2x}", b)
}
// Some non-empty sequence of ASCII letters, digits and underscores
fn escape(s: String) -> String {
// Special-case the empty string
if s.len() == 0 {
return "_".to_string();
}
let bytes = s.into_bytes();
let mut iter = bytes.iter();
let mut out = String::new();
// Only alphanumeric in the first byte
let x = *iter.next().expect("Already checked len > 0");
let first = match x {
b'a'..=b'z' | b'A'..=b'Z' => unsafe { String::from_utf8_unchecked(vec![x]) },
_ => escape_one(x),
};
out.push_str(&first);
for x in iter {
let next = match x {
b'a'..=b'z' | b'A'..=b'Z' | b'0'..=b'9' => unsafe {
String::from_utf8_unchecked(vec![*x])
},
_ => escape_one(*x),
};
out.push_str(&next);
}
out
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_escape() {
assert_eq!(escape("".to_string()), "_");
assert_eq!(escape("foo".to_string()), "foo");
assert_eq!(escape("foo@bar".to_string()), "foo_40bar");
assert_eq!(escape("foo_bar".to_string()), "foo_5fbar");
assert_eq!(escape("foo__@__bar".to_string()), "foo_5f_5f_40_5f_5fbar");
assert_eq!(escape("1foo".to_string()), "_31foo");
}
}
impl Connection {
pub fn new(params: HashMap<&str, super::Variant>) -> Result<Self, dbus::tree::MethodErr> {
let err = Err(dbus::tree::MethodErr::no_arg());
let acct = match params.get("account") {
Some(variant) => match variant.0.as_str() {
Some(str) => str.to_string(),
None => return err,
},
None => return err,
};
let id = escape(acct.to_owned());
let password = match params.get("password") {
Some(variant) => match variant.0.as_str() {
Some(str) => str.to_string(),
None => return err,
},
None => return err,
};
let mut dbfile = directories::ProjectDirs::from("gs", "ur", "telepathy-padfoot")
.ok_or_else(|| tree::MethodErr::no_arg())
.and_then(|p| Ok(p.data_local_dir().to_path_buf()))?;
dbfile.push(&id);
dbfile.push("db.sqlite3");
// FIXME: how to give it access to the connection (initialized later)?
let id2 = id.clone();
let f = move |_c: &Context, e: Event| {
match e {
Event::Info(msg) => println!("Connection<{}>: INFO: {}", id2, msg),
Event::Warning(msg) => println!("Connection<{}>: WARN : {}", id2, msg),
Event::Error(msg) | Event::ErrorNetwork(msg) | Event::ErrorSelfNotInGroup(msg) => {
println!("Connection<{}>: ERR : {}", id2, msg)
}
_ => println!("Connection<{}>: unhandled event received: {:?}", id2, e),
};
};
let ctx =
Context::new(Box::new(f), "telepathy-padfoot".to_string(), dbfile).map_err(|e| {
println!("Connection<{}>: couldn't get delta context: {}", id, e);
tree::MethodErr::no_arg() // FIXME: better error handling
})?;
ctx.set_config(Config::Addr, Some(&acct))
.map_err(|_e| tree::MethodErr::no_arg())?;
ctx.set_config(Config::MailPw, Some(&password))
.map_err(|_e| tree::MethodErr::no_arg())?;
ctx.set_config(Config::SentboxWatch, Some(&"Sent"))
.map_err(|_e| tree::MethodErr::no_arg())?;
if !ctx.is_configured() {
ctx.configure();
};
Ok(Connection {
id: id,
ctx: Arc::new(RwLock::new(ctx)),
})
}
// This is run inside its own thread
//
// FIXME: running several +process+ loops sure is convenient, but it also
// seems inefficient...
pub fn run(self) {
let bus = self.bus();
let path = self.path();
let ctx = self.ctx.clone();
let c_rc = std::rc::Rc::new(self);
let f = tree::Factory::new_fn::<()>();
let iface = telepathy::connection_server(&f, (), move |_| c_rc.clone());
let mut tree = f.tree(());
tree = tree.add(f.object_path(path, ()).introspectable().add(iface));
tree = tree.add(f.object_path("/", ()).introspectable());
// Setup DBus connection
let mut c = match LocalConnection::new_session() {
Ok(c) => c,
Err(e) => {
println!("Failed to establish DBUS session for {}: {}", bus, e);
return; // Leave early
}
};
tree.start_receive(&c);
match c.request_name(bus.clone(), false, false, true) {
Ok(RequestNameReply::Exists) => {
println!("Another process is already registered on {}", bus);
return;
}
Err(e) => {
println!("Failed to register {}: {}", bus, e);
return;
}
_ => println!("{} listening on {}", c.unique_name(), bus), // All other responses we can get are a success
};
// Set up delta jobs last in case registering to DBUS fails
// "Borrowed" from https://github.com/deltachat/deltachat-core-rust/blob/master/examples/simple.rs
let running = Arc::new(RwLock::new(true));
let inbox_ctx = ctx.clone();
let r1 = running.clone();
let _t1 = thread::spawn(move || {
while *r1.read().unwrap() {
dc::job::perform_inbox_jobs(&inbox_ctx.read().unwrap());
if *r1.read().unwrap() {
dc::job::perform_inbox_fetch(&inbox_ctx.read().unwrap());
if *r1.read().unwrap() {
dc::job::perform_inbox_idle(&inbox_ctx.read().unwrap());
}
}
}
});
let smtp_ctx = ctx.clone();
let r1 = running.clone();
let _t2 = thread::spawn(move || {
while *r1.read().unwrap() {
dc::job::perform_smtp_jobs(&smtp_ctx.read().unwrap());
if *r1.read().unwrap() {
dc::job::perform_smtp_idle(&smtp_ctx.read().unwrap());
}
}
});
let mvbox_ctx = ctx.clone();
let r1 = running.clone();
let _t3 = thread::spawn(move || {
while *r1.read().unwrap() {
dc::job::perform_mvbox_fetch(&mvbox_ctx.read().unwrap());
if *r1.read().unwrap() {
dc::job::perform_mvbox_idle(&mvbox_ctx.read().unwrap());
}
}
});
let sentbox_ctx = ctx.clone();
let r1 = running.clone();
let _t4 = thread::spawn(move || {
while *r1.read().unwrap() {
dc::job::perform_sentbox_fetch(&sentbox_ctx.read().unwrap());
if *r1.read().unwrap() {
dc::job::perform_sentbox_idle(&sentbox_ctx.read().unwrap());
}
}
});
let r1 = running.clone();
while *r1.read().unwrap() {
match c.process(Duration::from_millis(100)) {
Err(e) => {
println!("Error processing: {}", e);
return;
}
_ => {}
}
// TODO: notice when the conn wants to exit
}
}
pub fn bus(&self) -> String {
CONN_BUS_NAME.to_owned() + "." + &self.id
}
pub fn path(&self) -> String {
CONN_OBJECT_PATH.to_owned() + "/" + &self.id
}
}
impl AsRef<dyn telepathy::Connection + 'static> for std::rc::Rc<Connection> {
fn as_ref(&self) -> &(dyn telepathy::Connection + 'static) {
&**self
}
}
impl telepathy::Connection for Connection {
fn connect(&self) -> Result<(), tree::MethodErr> {
Err(tree::MethodErr::no_arg()) // FIXME: should be NotImplemented?
}
fn disconnect(&self) -> Result<(), tree::MethodErr> {
println!("Connection<{}>::disconnect()", self.id);
Err(tree::MethodErr::no_arg()) // FIXME: should be NotImplemented?
}
fn get_interfaces(&self) -> Result<Vec<String>, tree::MethodErr> {
println!("Connection<{}>::get_interfaces()", self.id);
Err(tree::MethodErr::no_arg()) // FIXME: should be NotImplemented?
}
fn get_protocol(&self) -> Result<String, tree::MethodErr> {
println!("Connection<{}>::get_protocol()", self.id);
Ok(super::PROTO_NAME.to_string())
}
fn get_self_handle(&self) -> Result<u32, tree::MethodErr> {
println!("Connection<{}>::get_self_handle()", self.id);
Err(tree::MethodErr::no_arg()) // FIXME: should be NotImplemented?
}
fn get_status(&self) -> Result<u32, tree::MethodErr> {
println!("Connection<{}>::get_status()", self.id);
Err(tree::MethodErr::no_arg()) // FIXME: should be NotImplemented?
}
fn hold_handles(&self, handle_type: u32, handles: Vec<u32>) -> Result<(), tree::MethodErr> {
println!(
"Connection<{}>::hold_handles({}, {:?})",
self.id, handle_type, handles
);
Err(tree::MethodErr::no_arg()) // FIXME: should be NotImplemented?
}
fn inspect_handles(
&self,
handle_type: u32,
handles: Vec<u32>,
) -> Result<Vec<String>, tree::MethodErr> {
println!(
"Connection<{}>::inspect_handles({}, {:?})",
self.id, handle_type, handles
);
Err(tree::MethodErr::no_arg()) // FIXME: should be NotImplemented?
}
fn list_channels(
&self,
) -> Result<Vec<(dbus::Path<'static>, String, u32, u32)>, tree::MethodErr> {
println!("Connection<{}>::list_channels()", self.id);
Err(tree::MethodErr::no_arg()) // FIXME: should be NotImplemented?
}
fn release_handles(&self, handle_type: u32, handles: Vec<u32>) -> Result<(), tree::MethodErr> {
println!(
"Connection<{}>::release_handles({}, {:?})",
self.id, handle_type, handles
);
Err(tree::MethodErr::no_arg()) // FIXME: should be NotImplemented?
}
fn request_channel(
&self,
type_: &str,
handle_type: u32,
handle: u32,
suppress_handler: bool,
) -> Result<dbus::Path<'static>, tree::MethodErr> {
println!(
"Connection<{}>::request_channel({}, {}, {}, {})",
self.id, type_, handle_type, handle, suppress_handler
);
Err(tree::MethodErr::no_arg()) // FIXME: should be NotImplemented?
}
fn request_handles(
&self,
handle_type: u32,
identifiers: Vec<&str>,
) -> Result<Vec<u32>, tree::MethodErr> {
println!(
"Connection<{}>::request_handles({}, {:?})",
self.id, handle_type, identifiers
);
Err(tree::MethodErr::no_arg()) // FIXME: should be NotImplemented?
}
fn add_client_interest(&self, tokens: Vec<&str>) -> Result<(), tree::MethodErr> {
println!("Connection<{}>::add_client_interest({:?})", self.id, tokens);
Err(tree::MethodErr::no_arg()) // FIXME: should be NotImplemented?
}
fn remove_client_interest(&self, tokens: Vec<&str>) -> Result<(), tree::MethodErr> {
println!(
"Connection<{}>::remove_client_interest({:?})",
self.id, tokens
);
Err(tree::MethodErr::no_arg()) // FIXME: should be NotImplemented?
}
fn interfaces(&self) -> Result<Vec<String>, tree::MethodErr> {
println!("Connection<{}>::interfaces()", self.id);
Err(tree::MethodErr::no_arg()) // FIXME: should be NotImplemented?
}
fn self_handle(&self) -> Result<u32, tree::MethodErr> {
println!("Connection<{}>::self_handle()", self.id);
Err(tree::MethodErr::no_arg()) // FIXME: should be NotImplemented?
}
fn self_id(&self) -> Result<String, tree::MethodErr> {
println!("Connection<{}>::self_id()", self.id);
Err(tree::MethodErr::no_arg()) // FIXME: should be NotImplemented?
}
fn status(&self) -> Result<u32, tree::MethodErr> {
println!("Connection<{}>::status()", self.id);
Err(tree::MethodErr::no_arg()) // FIXME: should be NotImplemented?
}
fn has_immortal_handles(&self) -> Result<bool, tree::MethodErr> {
println!("Connection<{}>::has_immortal_handles()", self.id);
Ok(true)
}
}