~nicohman/signal-rs

5fae6c5ddbe1021b0690f601caab3a5a70cdd75f — nicohman 7 months ago 5dbe83b
Remove SCLIBackend and add proxy to SCLISession
4 files changed, 60 insertions(+), 40 deletions(-)

M qml/ChatList.qml
M qml/Theme.qml
M src/main.rs
M src/scli.rs
M qml/ChatList.qml => qml/ChatList.qml +2 -0
@@ 38,8 38,10 @@ Page {
                Column {
                    Label {
                        text: name
                        color: theme.text
                    }
                    Label {
                        color: theme.text
                        text: last
                    }          
                }

M qml/Theme.qml => qml/Theme.qml +1 -0
@@ 22,6 22,7 @@ Item {
			theme.message = "#2e2e2e";
			theme.highlightedText = "white";
			theme.outgoing = "#2c6bed";
			theme.text = "white";
			break;
		case 'System':
			theme.background = systemPalette.window;

M src/main.rs => src/main.rs +23 -12
@@ 38,11 38,14 @@ extern crate serde_json;

extern crate tempfile;
use std::io::Read;
use std::sync::Arc;
extern crate tokio;
use std::ffi::CStr;
extern crate tungstenite;
#[macro_use]
extern crate lazy_static;
use dbus::*;
use dbus_tokio::connection;
use qmetaobject::QAbstractListModel;
use qmetaobject::*;
use tokio::sync::mpsc::*;


@@ 79,7 82,7 @@ pub trait SignalBackend {
#[derive(QObject, Default)]
pub struct SignalUI {
    base: qt_base_class!(trait QObject),
    state: Option<SignalState<scli::SCLISession>>,
    state: Option<SignalState>,
    name: qt_property!(QString; NOTIFY name_changed),
    name_changed: qt_signal!(),
    init_state: qt_method!(fn(&mut self)),


@@ 268,10 271,19 @@ impl SignalUI {
        let l_res_sender = res_sender.clone();
        let (req_sender, req_receiver) = unbounded_channel::<SignalRequest>();
        let clconf = config.clone();
        let (resource, conn) = connection::new_session_sync().expect("Couldn't connect to DBus");
        qmetaobject::future::execute_async(async {
            let err = resource.await;
            println!("Lost connection to DBus: {}", err);
        });
        match config.backend {
            Backend::SignalCLI => {
                let mut state: SignalState<scli::SCLISession> =
                    SignalState::new(req_sender.clone(), l_res_sender.clone(), config.clone());
                let mut state: SignalState = SignalState::new(
                    req_sender.clone(),
                    l_res_sender.clone(),
                    config.clone(),
                    conn.clone(),
                );
                state.user_tel = config.number.clone();
                state.show_view("chats".to_string());
                self.state = Some(state);


@@ 300,14 312,15 @@ impl SignalUI {
                }
                // Inspired in part by https://github.com/boxdot/gurk-rs
                Backend::SignalCLI => {
                    scli::SCLISession::run(res_sender, req_receiver, clconf.scli.unwrap()).await;
                    scli::SCLISession::run(res_sender, req_receiver, clconf.scli.unwrap(), conn)
                        .await;
                }
            }
        });
    }
}
/// The main state manager/UI controller
pub struct SignalState<T: SignalBackend + ?Sized> {
pub struct SignalState {
    pub current: Option<String>,
    pub req_sender: UnboundedSender<SignalRequest>,
    pub last_id: Rc<RefCell<Option<String>>>,


@@ 315,7 328,7 @@ pub struct SignalState<T: SignalBackend + ?Sized> {
    pub current_chat: Rc<RefCell<Option<String>>>,
    pub user_tel: Option<String>,
    pub backend: Backend,
    pub sbackend: std::boxed::Box<T>,
    pub sbackend: std::boxed::Box<scli::SCLISession>,
    pub config: Rc<RefCell<Config>>,
}
/// An enum that represents a view that can be displayed.


@@ 331,16 344,14 @@ pub enum View {
    EditContact(String),
    Settings,
}
impl<T> SignalState<T>
where
    T: SignalBackend,
{
impl SignalState {
    /// Build a new signal state using a websocket connection
    pub fn new(
        req_sender: UnboundedSender<SignalRequest>,
        res_sender: glib::Sender<SignalResponse>,
        config: Config,
    ) -> SignalState<T> {
        conn: Arc<nonblock::SyncConnection>,
    ) -> SignalState {
        SignalState {
            current: None,
            req_sender,


@@ 350,7 361,7 @@ where
            user_tel: None,
            backend: config.backend,
            config: Rc::new(RefCell::new(config.clone())),
            sbackend: std::boxed::Box::new(T::new(res_sender, config)),
            sbackend: std::boxed::Box::new(scli::SCLISession::new(res_sender, config, conn)),
        }
    }
    /// Shows the given view in the app_box

M src/scli.rs => src/scli.rs +34 -28
@@ 9,8 9,9 @@ use pallet::Store;
use std::fs;
use std::io::BufRead;
use std::io::Write;
use std::path::*;
use std::path::{Path, PathBuf};
use std::process::Stdio;
use std::sync::Arc;
use std::time::Duration;
lazy_static! {
    static ref SCLI_ATTACHMENT_DIRECTORY: String = home::home_dir()


@@ 32,6 33,7 @@ pub struct SCLISession {
    pub config: SignalCLIConfig,
    pub res_sender: glib::Sender<SignalResponse>,
    pub groups: HashMap<String, Group>,
    pub proxy: nonblock::Proxy<'static, Arc<nonblock::SyncConnection>>,
}
impl SCLISession {
    pub fn merge_ids(&self, from: String, to: String) {


@@ 71,12 73,8 @@ impl SCLISession {
        res_sender: glib::Sender<SignalResponse>,
        mut req_receiver: UnboundedReceiver<SignalRequest>,
        config: SignalCLIConfig,
        conn: Arc<nonblock::SyncConnection>,
    ) {
        let (resource, conn) = connection::new_session_sync().expect("Couldn't connect to DBus");
        qmetaobject::future::execute_async(async {
            let err = resource.await;
            println!("Lost connection to DBus: {}", err);
        });
        let (line_sender, mut line_receiver) =
            tokio::sync::mpsc::unbounded_channel::<SignalCLIContainer>();
        let clconf = config.clone();


@@ 352,9 350,7 @@ impl SCLISession {
            .doc
            .clone())
    }
}
impl SignalBackend for SCLISession {
    fn avatar(&self, id: &str) -> Option<String> {
    pub fn avatar(&self, id: &str) -> Option<String> {
        let opts = vec![
            "profile".to_string(),
            "contact".to_string(),


@@ 370,13 366,17 @@ impl SignalBackend for SCLISession {
        }
        None
    }
    fn set_avatar(&self, id: String, data: &[u8]) -> String {
    pub fn set_avatar(&self, id: String, data: &[u8]) -> String {
        let path = PathBuf::from(SCLI_AVATAR_DIRECTORY.to_owned()).join(format!("contact-{}", id));
        let mut fd = fs::File::create(&path).expect("Couldn't create avatar file");
        fd.write_all(data).expect("Couldn't write to avatar file");
        return path.into_os_string().into_string().unwrap();
    }
    fn new(res_sender: glib::Sender<SignalResponse>, config: Config) -> SCLISession {
    pub fn new(
        res_sender: glib::Sender<SignalResponse>,
        config: Config,
        conn: Arc<nonblock::SyncConnection>,
    ) -> SCLISession {
        let scli_config = config.scli.expect("No configured SCLI settings");
        let data_dir = scli_config.data_dir.clone().replace(
            "~",


@@ 405,16 405,23 @@ impl SignalBackend for SCLISession {
        contact_store
            .index_all()
            .expect("Couldn't index contact store");
        let proxy = nonblock::Proxy::new(
            "org.asamk.Signal",
            "/org/asamk/Signal",
            Duration::from_secs(10),
            conn.clone(),
        );
        let session = SCLISession {
            res_sender,
            config: scli_config,
            msg_store,
            contact_store,
            groups: HashMap::new(),
            proxy,
        };
        session
    }
    fn messages_id<'a>(&'a self, chat_id: &str) -> Result<BTreeMap<i64, SignalMessage>> {
    pub fn messages_id<'a>(&'a self, chat_id: &str) -> Result<BTreeMap<i64, SignalMessage>> {
        let messages = self
            .msg_store
            .search(format!("chat_id:{}", chat_id).as_str())?


@@ 427,7 434,7 @@ impl SignalBackend for SCLISession {
            })
            .collect())
    }
    fn messages<'a>(
    pub fn messages<'a>(
        &'a self,
    ) -> Result<BTreeMap<String, BTreeMap<i64, SignalMessage>>, anyhow::Error> {
        let mut map: BTreeMap<String, BTreeMap<i64, SignalMessage>> = BTreeMap::new();


@@ 443,7 450,7 @@ impl SignalBackend for SCLISession {
        }
        Ok(map)
    }
    fn message<'a>(&'a self, chat_id: &str, sent_at: i64) -> Result<SignalMessage> {
    pub fn message<'a>(&'a self, chat_id: &str, sent_at: i64) -> Result<SignalMessage> {
        Ok(self
            .msg_store
            .search(format!("chat_id:{} AND sent_at:{}", chat_id, sent_at).as_str())?


@@ 455,7 462,7 @@ impl SignalBackend for SCLISession {
            .inner
            .clone())
    }
    fn contact<'a>(&'a self, tel: &str) -> Result<Contact> {
    pub fn contact<'a>(&'a self, tel: &str) -> Result<Contact> {
        Ok(self
            .contact_store
            .search(format!("tel:{}", tel).as_str())?


@@ 467,10 474,10 @@ impl SignalBackend for SCLISession {
            .inner
            .clone())
    }
    fn groups<'a>(&'a self) -> HashMap<String, Group> {
    pub fn groups<'a>(&'a self) -> HashMap<String, Group> {
        self.groups.clone()
    }
    fn contacts<'a>(&'a self) -> HashMap<String, Contact> {
    pub fn contacts<'a>(&'a self) -> HashMap<String, Contact> {
        let map = self
            .contact_store
            .all()


@@ 483,7 490,7 @@ impl SignalBackend for SCLISession {
            .collect();
        map
    }
    fn process_response(&mut self, response: SignalResponse) {
    pub fn process_response(&mut self, response: SignalResponse) {
        match response {
            SignalResponse::RmMessage { chat_id, sent_at } => {
                if let Some(doc) = self.message_doc(&chat_id, sent_at).ok() {


@@ 541,26 548,25 @@ impl SignalBackend for SCLISession {
                from,
                emoji,
            } => {
                println!("{}\n{}\n{}", target, target_timestamp, emoji.clone());

                let reaction = Reaction {
                    emoji,
                    source: from,
                    timestamp,
                };
                let mut fetched = self
                if let Some(fetched) = self
                    .msg_store
                    .search(format!("source:{} AND sent_at:{}", target, target_timestamp).as_str())
                    .expect("Couldn't find reaction message")
                    .expect("Couldn't search for reaction message")
                    .hits
                    .into_iter()
                    .next()
                    .unwrap()
                    .doc;
                fetched.inner.reactions.push(reaction);
                self.msg_store
                    .update(&fetched)
                    .expect("Couldn't update reaction message");
                {
                    let mut fetched = fetched.doc;
                    fetched.inner.reactions.push(reaction);
                    self.msg_store
                        .update(&fetched)
                        .expect("Couldn't update reaction message");
                }
            }
            _ => {}
        }