Compare commits
9 Commits
786f199a8d
...
rust
Author | SHA1 | Date | |
---|---|---|---|
29ea694b1e | |||
eec8669b88 | |||
e99d824227 | |||
f57cdf3cdc | |||
039807bb32 | |||
fd321e02d8 | |||
3142134360 | |||
70b24f0e14 | |||
77a257892a |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1 +1,2 @@
|
||||
/purple
|
||||
/target
|
||||
|
1181
Cargo.lock
generated
1181
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
27
Cargo.toml
27
Cargo.toml
@@ -2,23 +2,30 @@
|
||||
name = "purple-plugin-delta"
|
||||
version = "0.1.0"
|
||||
authors = ["Nick Thomas <delta@ur.gs>"]
|
||||
rust-version = "1.56"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
name = "delta"
|
||||
path = "src/delta.rs"
|
||||
name = "purple_delta"
|
||||
path = "src/lib.rs"
|
||||
crate-type = ["dylib"]
|
||||
|
||||
[dependencies]
|
||||
libc = "*"
|
||||
glib-sys = "*"
|
||||
purple-sys = { git = "https://github.com/lupine/libpurple-rust", branch="with-flared" }
|
||||
deltachat = { git = "https://github.com/deltachat/deltachat-core-rust", tag="v1.51.0" }
|
||||
lazy_static = "1.4.0"
|
||||
deltachat = { git = "https://github.com/deltachat/deltachat-core-rust", tag = "1.61.0" }
|
||||
lazy_static = "*"
|
||||
log = "*"
|
||||
openssl = "*"
|
||||
os_pipe = "*"
|
||||
purple-rs = { git = "https://github.com/Flared/purple-rs", branch = "master" }
|
||||
serde = "*"
|
||||
|
||||
[profile.dev]
|
||||
debug = 0
|
||||
## Keep in sync with deltachat-core-rust ##
|
||||
[dependencies.async-std]
|
||||
version = "1"
|
||||
features = ["unstable"]
|
||||
|
||||
[profile.release]
|
||||
lto = true
|
||||
|
||||
|
||||
[patch.crates-io]
|
||||
openssl-sys = { git = "https://github.com/sfackler/rust-openssl", branch = "master" }
|
||||
|
7
Makefile
Normal file
7
Makefile
Normal file
@@ -0,0 +1,7 @@
|
||||
run:
|
||||
cargo build
|
||||
rm -rf purple/plugins
|
||||
mkdir -p purple/plugins
|
||||
ln -s ../../target/debug/libpurple_delta.so purple/plugins/libpurple_delta.so
|
||||
ldd purple/plugins/libpurple_delta.so
|
||||
pidgin -d -c purple
|
21
README.md
21
README.md
@@ -33,22 +33,15 @@ Starting again from scratch in Rust. So currently, nothing works. TODO list:
|
||||
- [ ] Send/receive video messages
|
||||
- [ ] Send/receive arbitrary attachments
|
||||
|
||||
|
||||
|
||||
## Build
|
||||
|
||||
There are some licensing issues at present, so you shouldn't build this plugin.
|
||||
To get a `target/debug/libpurple_delta.so`, just run `cargo build`.
|
||||
|
||||
`deltachat-core-rust` uses a vendored openssl 1, unconditionally links it, and
|
||||
is MPL-licensed.
|
||||
|
||||
`purple-plugin-delta` is GPLv3 without the [OpenSSL exemption](https://people.gnome.org/~markmc/openssl-and-the-gpl.html)
|
||||
|
||||
`libpurple` itself is GPLv2 without the OpenSSL exemption.
|
||||
|
||||
There's no point to `purple-plugin-delta` adding the OpenSSL exemption because
|
||||
`libpurple` lacks it, and in any event, it will be unnecessary with the next
|
||||
major version of OpenSSL. So, time should resolve this for us one way or another.
|
||||
Since purple-plugin-delta is made to link against libpurple, which is GPLv2
|
||||
without the "OpenSSL exemption", distributing something that linked against
|
||||
OpenSSL 1 would be a licensing violation. Instead, we configure the build system
|
||||
so we statically link against a vendored OpenSSL 3 instead. This has only been
|
||||
possible since 2021-09-07.
|
||||
|
||||
Significant code using the WTFPL includes the [libpurple-rust bindings](https://github.com/sbwtw/libpurple-rust)
|
||||
and the [pidgin-wechat plugin](https://github.com/sbwtw/pidgin-wechat), which
|
||||
@@ -57,7 +50,7 @@ against this mess.
|
||||
|
||||
## Use
|
||||
|
||||
The easiest way to use this is to copy the `libdelta.so` file into
|
||||
The easiest way to use this is to copy the `libpurple_delta.so` file into
|
||||
`~/.purple/plugins`. When running pidgin, you'll now have the option to add
|
||||
a "Delta Chat" account.
|
||||
|
||||
|
@@ -1 +1 @@
|
||||
1.50.0
|
||||
1.56.0
|
||||
|
136
src/chat_info.rs
Normal file
136
src/chat_info.rs
Normal file
@@ -0,0 +1,136 @@
|
||||
// This is a copy of https://github.com/Flared/purple-icq/blob/master/src/chat_info.rs
|
||||
|
||||
use super::purple;
|
||||
use lazy_static::lazy_static;
|
||||
use std::ffi::CString;
|
||||
|
||||
lazy_static! {
|
||||
pub static ref SN: CString = CString::new("sn").unwrap();
|
||||
pub static ref SN_NAME: CString = CString::new("Chat ID").unwrap();
|
||||
pub static ref STAMP: CString = CString::new("stamp").unwrap();
|
||||
pub static ref TITLE: CString = CString::new("title").unwrap();
|
||||
pub static ref GROUP: CString = CString::new("group").unwrap();
|
||||
pub static ref STATE: CString = CString::new("state").unwrap();
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct MemberRole(String);
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct PartialChatInfo {
|
||||
pub sn: String,
|
||||
pub title: String,
|
||||
pub group: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct ChatInfo {
|
||||
pub stamp: Option<String>,
|
||||
pub group: Option<String>,
|
||||
pub sn: String,
|
||||
pub title: String,
|
||||
pub about: Option<String>,
|
||||
pub members_version: String,
|
||||
pub info_version: String,
|
||||
pub members: Vec<ChatMember>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ChatMember {
|
||||
pub sn: String,
|
||||
pub friendly_name: Option<String>,
|
||||
pub role: MemberRole,
|
||||
pub last_seen: Option<u64>,
|
||||
pub first_name: Option<String>,
|
||||
pub last_name: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ChatInfoVersion {
|
||||
pub members_version: String,
|
||||
pub info_version: String,
|
||||
}
|
||||
|
||||
impl MemberRole {
|
||||
pub fn as_flags(&self) -> purple::PurpleConvChatBuddyFlags {
|
||||
match self.0.as_str() {
|
||||
"admin" => purple::PurpleConvChatBuddyFlags::PURPLE_CBFLAGS_OP,
|
||||
"readonly" => purple::PurpleConvChatBuddyFlags::PURPLE_CBFLAGS_NONE,
|
||||
_ => purple::PurpleConvChatBuddyFlags::PURPLE_CBFLAGS_VOICE,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialChatInfo {
|
||||
pub fn from_hashtable(table: &purple::StrHashTable) -> Option<Self> {
|
||||
Some(Self {
|
||||
group: table.lookup(&GROUP).map(Into::into),
|
||||
sn: table.lookup(&SN)?.into(),
|
||||
title: table.lookup(&TITLE)?.into(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn as_hashtable(&self) -> purple::StrHashTable {
|
||||
let mut table = purple::StrHashTable::default();
|
||||
table.insert(&SN, &self.sn);
|
||||
if let Some(group) = &self.group {
|
||||
table.insert(&GROUP, &group);
|
||||
}
|
||||
table.insert(&TITLE, &self.title);
|
||||
table
|
||||
}
|
||||
}
|
||||
|
||||
impl ChatInfo {
|
||||
pub fn as_partial(&self) -> PartialChatInfo {
|
||||
PartialChatInfo {
|
||||
sn: self.sn.clone(),
|
||||
title: self.title.clone(),
|
||||
group: self.group.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn need_update(&self, new_version: &ChatInfoVersion) -> bool {
|
||||
self.members_version < new_version.members_version
|
||||
|| self.info_version < new_version.info_version
|
||||
}
|
||||
}
|
||||
/*
|
||||
impl From<icq::client::GetChatInfoResponseData> for ChatInfo {
|
||||
fn from(info: icq::client::GetChatInfoResponseData) -> Self {
|
||||
Self {
|
||||
sn: info.sn,
|
||||
stamp: Some(info.stamp),
|
||||
title: info.name,
|
||||
members_version: info.members_version,
|
||||
info_version: info.info_version,
|
||||
about: info.about,
|
||||
members: info
|
||||
.members
|
||||
.into_iter()
|
||||
.map(|m| ChatMember {
|
||||
sn: m.sn,
|
||||
role: MemberRole(m.role),
|
||||
last_seen: m.user_state.last_seen.and_then(|t| match t {
|
||||
0 => None,
|
||||
t => Some(t),
|
||||
}),
|
||||
friendly_name: m.friendly,
|
||||
first_name: m.anketa.first_name,
|
||||
last_name: m.anketa.last_name,
|
||||
})
|
||||
.collect(),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<icq::client::events::HistDlgStateMChatState> for ChatInfoVersion {
|
||||
fn from(info: icq::client::events::HistDlgStateMChatState) -> Self {
|
||||
Self {
|
||||
members_version: info.members_version,
|
||||
info_version: info.info_version,
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
188
src/delta.rs
188
src/delta.rs
@@ -1,188 +0,0 @@
|
||||
extern crate purple_sys;
|
||||
extern crate glib_sys;
|
||||
extern crate libc;
|
||||
#[macro_use]
|
||||
extern crate lazy_static;
|
||||
|
||||
/*
|
||||
mod pointer;
|
||||
mod server;
|
||||
mod user;
|
||||
mod chatroom;
|
||||
mod message;
|
||||
*/
|
||||
use std::os::raw::{c_void, c_char};
|
||||
use std::ptr::null_mut;
|
||||
use std::boxed::Box;
|
||||
use std::ffi::CString; // CStr
|
||||
//use std::sync::RwLock;
|
||||
|
||||
use purple_sys::*;
|
||||
use purple_sys::PurpleType;
|
||||
//use purple_sys::PurpleConnectionState;
|
||||
use purple_sys::PurpleStatusPrimitive;
|
||||
//use purple_sys::PurpleRoomlistFieldType;
|
||||
//use message::*;
|
||||
//use pointer::Pointer;
|
||||
//use server::ACCOUNT;
|
||||
//use server::{send_im, send_chat, find_blist_chat, find_chat_token};
|
||||
|
||||
use glib_sys::{GHashTable, GList};
|
||||
|
||||
const TRUE: i32 = 1;
|
||||
const FALSE: i32 = 0;
|
||||
|
||||
lazy_static!{
|
||||
// static ref PLUGIN: RwLock<Pointer> = RwLock::new(Pointer::new());
|
||||
static ref ICON_FILE: CString = CString::new("delta").unwrap();
|
||||
// static ref DELTA_CATEGORY: CString = CString::new("Delta Chat").unwrap();
|
||||
}
|
||||
|
||||
fn append_item(list: *mut GList, item: *mut c_void) -> *mut GList {
|
||||
unsafe {
|
||||
glib_sys::g_list_append(list as *mut glib_sys::GList, item as *mut libc::c_void) as
|
||||
*mut GList
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" fn list_icon(_: *mut PurpleAccount, _: *mut PurpleBuddy) -> *const c_char {
|
||||
ICON_FILE.as_ptr()
|
||||
}
|
||||
|
||||
extern "C" fn status_types(_: *mut PurpleAccount) -> *mut GList {
|
||||
|
||||
let mut list: *mut GList = null_mut();
|
||||
|
||||
let available = CString::new("available").unwrap();
|
||||
let available_name = CString::new("Available").unwrap();
|
||||
let offline = CString::new("offline").unwrap();
|
||||
let offline_name = CString::new("Offline").unwrap();
|
||||
let nick = CString::new("nick").unwrap();
|
||||
|
||||
let status = unsafe {
|
||||
purple_status_type_new_with_attrs(
|
||||
PurpleStatusPrimitive::PURPLE_STATUS_AVAILABLE,
|
||||
available.as_ptr(),
|
||||
available_name.as_ptr(),
|
||||
TRUE,
|
||||
TRUE,
|
||||
FALSE,
|
||||
nick.as_ptr(),
|
||||
nick.as_ptr(),
|
||||
purple_value_new(PurpleType::PURPLE_TYPE_STRING),
|
||||
null_mut() as *mut c_void,
|
||||
)
|
||||
};
|
||||
list = append_item(list, status as *mut c_void);
|
||||
|
||||
let status = unsafe {
|
||||
purple_status_type_new_with_attrs(
|
||||
PurpleStatusPrimitive::PURPLE_STATUS_OFFLINE,
|
||||
offline.as_ptr(),
|
||||
offline_name.as_ptr(),
|
||||
TRUE,
|
||||
TRUE,
|
||||
FALSE,
|
||||
nick.as_ptr(),
|
||||
nick.as_ptr(),
|
||||
purple_value_new(PurpleType::PURPLE_TYPE_STRING),
|
||||
null_mut() as *mut c_void,
|
||||
)
|
||||
};
|
||||
list = append_item(list, status as *mut c_void);
|
||||
|
||||
list
|
||||
}
|
||||
|
||||
unsafe extern "C" fn login(account: *mut PurpleAccount) {
|
||||
println!("account: {:?}", account);
|
||||
}
|
||||
|
||||
extern "C" fn chat_info(_: *mut PurpleConnection) -> *mut GList {
|
||||
|
||||
let list: *mut GList = null_mut();
|
||||
|
||||
list
|
||||
}
|
||||
|
||||
unsafe extern "C" fn join_chat(gc: *mut PurpleConnection, components: *mut GHashTable) {
|
||||
println!("join_chat: {:?}, {:?}", gc, components);
|
||||
}
|
||||
|
||||
extern "C" fn chat_info_defaults(_: *mut PurpleConnection, _: *const c_char) -> *mut GHashTable {
|
||||
|
||||
let table: *mut GHashTable = null_mut();
|
||||
|
||||
table
|
||||
}
|
||||
|
||||
extern "C" fn close(_: *mut PurpleConnection) {}
|
||||
|
||||
extern "C" fn buddy_list(gc: *mut PurpleConnection) -> *mut PurpleRoomlist {
|
||||
let buddies = unsafe { purple_roomlist_new(purple_connection_get_account(gc)) };
|
||||
|
||||
buddies
|
||||
}
|
||||
|
||||
extern "C" fn callback(_plugin: *mut PurplePlugin) -> i32 {
|
||||
TRUE
|
||||
}
|
||||
|
||||
// extern "C" fn action_cb(_: *mut PurplePluginAction) {
|
||||
//
|
||||
// }
|
||||
|
||||
extern "C" fn actions(_: *mut PurplePlugin, _: *mut c_void) -> *mut GList {
|
||||
|
||||
let list: *mut GList = null_mut();
|
||||
|
||||
list
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn purple_init_plugin(plugin: *mut PurplePlugin) -> i32 {
|
||||
// save plugin pointer
|
||||
// PLUGIN.write().unwrap().set(plugin as *mut c_void);
|
||||
|
||||
let id = CString::new("prpl-delta").unwrap();
|
||||
let name = CString::new("Delta Chat").unwrap();
|
||||
let version = CString::new("0.1.0").unwrap();
|
||||
let summary = CString::new("Delta Chat is an email-based instant messaging solution").unwrap();
|
||||
let description = CString::new("See https://delta.chat for more information").unwrap();
|
||||
let author = CString::new("Nick Thomas <delta@ur.gs>").unwrap();
|
||||
let home_page = CString::new("https://delta.chat").unwrap();
|
||||
|
||||
let mut info = Box::new(PurplePluginInfo::new());
|
||||
let mut extra_info = Box::new(PurplePluginProtocolInfo::new());
|
||||
|
||||
unsafe {
|
||||
|
||||
extra_info.list_icon = Some(list_icon);
|
||||
extra_info.status_types = Some(status_types);
|
||||
extra_info.login = Some(login);
|
||||
extra_info.close = Some(close);
|
||||
extra_info.roomlist_get_list = Some(buddy_list);
|
||||
extra_info.chat_info = Some(chat_info);
|
||||
extra_info.chat_info_defaults = Some(chat_info_defaults);
|
||||
//extra_info.chat_send = Some(send_chat);
|
||||
extra_info.join_chat = Some(join_chat);
|
||||
//extra_info.find_blist_chat = Some(find_blist_chat);
|
||||
//extra_info.send_im = Some(send_im);
|
||||
|
||||
info.id = id.into_raw();
|
||||
info.name = name.into_raw();
|
||||
info.version = version.into_raw();
|
||||
info.summary = summary.into_raw();
|
||||
info.description = description.into_raw();
|
||||
info.author = author.into_raw();
|
||||
info.homepage = home_page.into_raw();
|
||||
info.load = Some(callback);
|
||||
info.actions = Some(actions);
|
||||
info.extra_info = Box::into_raw(extra_info) as *mut c_void;
|
||||
|
||||
(*plugin).info = Box::into_raw(info);
|
||||
};
|
||||
|
||||
unsafe { purple_plugin_register(plugin) }
|
||||
}
|
||||
|
1
src/delta/mod.rs
Normal file
1
src/delta/mod.rs
Normal file
@@ -0,0 +1 @@
|
||||
pub mod system;
|
252
src/delta/system.rs
Normal file
252
src/delta/system.rs
Normal file
@@ -0,0 +1,252 @@
|
||||
// use super::poller;
|
||||
// use super::protocol;
|
||||
use crate::logging;
|
||||
use crate::messages::{
|
||||
AccountInfo, DeltaSystemHandle, FdSender, GetChatInfoMessage, GetHistoryMessage,
|
||||
JoinChatMessage, PurpleMessage, SendMsgMessage, SystemMessage,
|
||||
};
|
||||
// use crate::{Handle, ChatInfo};
|
||||
use async_std::channel::{self, Receiver};
|
||||
use deltachat::accounts::Accounts;
|
||||
|
||||
const CHANNEL_CAPACITY: usize = 1024;
|
||||
|
||||
pub fn spawn() -> DeltaSystemHandle {
|
||||
let (input_rx, input_tx) = os_pipe::pipe().unwrap();
|
||||
let (system_tx, system_rx) = channel::bounded::<SystemMessage>(CHANNEL_CAPACITY);
|
||||
let (purple_tx, purple_rx) = channel::bounded(CHANNEL_CAPACITY);
|
||||
|
||||
let fd_sender = FdSender::new(input_tx, system_tx);
|
||||
|
||||
log::debug!("Starting async thread.");
|
||||
std::thread::spawn(move || run(fd_sender, purple_rx));
|
||||
|
||||
DeltaSystemHandle {
|
||||
input_rx,
|
||||
rx: system_rx,
|
||||
tx: purple_tx,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run(tx: FdSender<SystemMessage>, rx: Receiver<PurpleMessage>) {
|
||||
logging::set_thread_logger(logging::RemoteLogger(tx.clone()));
|
||||
log::info!("Starting Delta system");
|
||||
log::debug!("Performing delta accounts setup");
|
||||
|
||||
let mut config_dir = DeltaSystem::user_dir();
|
||||
config_dir.push("purple-plugin-delta");
|
||||
|
||||
let accounts =
|
||||
async_std::task::block_on(Accounts::new("purple-plugin-delta".into(), config_dir)).unwrap();
|
||||
|
||||
let mut system = DeltaSystem::new(tx, rx, accounts);
|
||||
async_std::task::block_on(system.run());
|
||||
}
|
||||
|
||||
pub struct DeltaSystem {
|
||||
tx: FdSender<SystemMessage>,
|
||||
rx: Receiver<PurpleMessage>,
|
||||
accounts: Accounts,
|
||||
}
|
||||
|
||||
impl DeltaSystem {
|
||||
fn new(tx: FdSender<SystemMessage>, rx: Receiver<PurpleMessage>, accounts: Accounts) -> Self {
|
||||
Self { tx, rx, accounts }
|
||||
}
|
||||
|
||||
fn user_dir() -> async_std::path::PathBuf {
|
||||
use std::os::unix::ffi::OsStrExt;
|
||||
|
||||
// SAFETY: We trust libpurple here
|
||||
let slice = unsafe { std::ffi::CStr::from_ptr(crate::purple_sys::purple_user_dir()) };
|
||||
let osstr = std::ffi::OsStr::from_bytes(slice.to_bytes());
|
||||
let path: &async_std::path::Path = osstr.as_ref();
|
||||
|
||||
path.into()
|
||||
}
|
||||
|
||||
async fn run(&mut self) {
|
||||
log::info!("Looping on messages");
|
||||
loop {
|
||||
let purple_message = match self.rx.recv().await {
|
||||
Ok(r) => r,
|
||||
Err(error) => {
|
||||
log::error!("Failed to receive message: {:?}", error);
|
||||
break;
|
||||
}
|
||||
};
|
||||
log::info!("Message: {:?}", purple_message);
|
||||
let result = match purple_message {
|
||||
PurpleMessage::Login(account_info) => self.login(account_info).await,
|
||||
PurpleMessage::JoinChat(m) => self.join_chat(m).await,
|
||||
PurpleMessage::SendMsg(m) => self.send_msg(m).await,
|
||||
PurpleMessage::GetChatInfo(m) => self.get_chat_info(m).await,
|
||||
PurpleMessage::GetHistory(m) => self.get_history(m).await,
|
||||
};
|
||||
if let Err(error) = result {
|
||||
log::error!("Error handling message: {}", error);
|
||||
}
|
||||
|
||||
logging::flush();
|
||||
}
|
||||
}
|
||||
|
||||
async fn login(&mut self, account_info: AccountInfo) -> std::result::Result<(), String> {
|
||||
log::debug!("login");
|
||||
let email_address = { account_info.protocol_data.email_address.clone() };
|
||||
let password = { account_info.protocol_data.imap_pass.clone() };
|
||||
let handle = &account_info.handle;
|
||||
|
||||
self.tx
|
||||
.connection_proxy(&handle)
|
||||
.set_state(purple::PurpleConnectionState::PURPLE_CONNECTING)
|
||||
.await;
|
||||
|
||||
// TODO: make this properly async
|
||||
let ctx = match self
|
||||
.accounts
|
||||
.get_all()
|
||||
.await
|
||||
.into_iter()
|
||||
.map(|id| async_std::task::block_on(self.accounts.get_account(id)).unwrap())
|
||||
.find(|ctx| {
|
||||
async_std::task::block_on(ctx.is_self_addr(&email_address)).unwrap_or(false)
|
||||
}) {
|
||||
None => {
|
||||
let id = self.accounts.add_account().await.unwrap();
|
||||
self.accounts.get_account(id).await.unwrap()
|
||||
}
|
||||
Some(ctx) => ctx,
|
||||
};
|
||||
|
||||
// Now transpose config into ctx. TODO: rest of the fields
|
||||
ctx.set_config(deltachat::config::Config::Addr, Some(&email_address))
|
||||
.await
|
||||
.unwrap();
|
||||
ctx.set_config(deltachat::config::Config::MailPw, Some(&password))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// FIXME: handle configuration failure nicely here. Right now we just panic.
|
||||
ctx.configure().await.unwrap();
|
||||
ctx.start_io().await;
|
||||
|
||||
async_std::task::spawn_local(DeltaSystem::deltachat_events(ctx));
|
||||
// Hint from deleted code:
|
||||
// self.tx.account_proxy(&handle).exec(|purple_account| ...);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn deltachat_events(ctx: deltachat::context::Context) {
|
||||
// TODO: loop until we're out of events
|
||||
let emitter = ctx.get_event_emitter();
|
||||
while let Some(event) = emitter.recv().await {
|
||||
println!("Received event {:?}", event);
|
||||
}
|
||||
|
||||
// FIXME: this is back to front. We need to stop_io to interrupt the loop
|
||||
ctx.stop_io().await;
|
||||
}
|
||||
|
||||
async fn get_chat_info(&mut self, message: GetChatInfoMessage) -> Result<(), String> {
|
||||
log::info!("Get chat info sn: {}", message.message_data.sn);
|
||||
/*
|
||||
let session = { message.protocol_data.session.read().await.clone().unwrap() };
|
||||
let chat_info_response = protocol::get_chat_info_by_sn(&session, &message.message_data.sn)
|
||||
.await
|
||||
.map_err(|e| format!("Failed to get chat info: {:?}", e))?;
|
||||
|
||||
self.tx
|
||||
.handle_proxy(&message.handle)
|
||||
.exec_no_return(move |plugin, protocol_data| {
|
||||
let chat_info = ChatInfo::from(chat_info_response);
|
||||
let connection = &mut protocol_data.connection;
|
||||
plugin.load_chat_info(connection, &chat_info);
|
||||
})
|
||||
.await;
|
||||
*/
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn join_chat(&mut self, message: JoinChatMessage) -> Result<(), String> {
|
||||
log::info!("Joining stamp: {}", message.message_data.stamp);
|
||||
/*
|
||||
let session = { message.protocol_data.session.read().await.clone().unwrap() };
|
||||
let stamp = message.message_data.stamp;
|
||||
// Handle shareable URLs: https://icq.im/XXXXXXXXXXXXXX
|
||||
let stamp = if stamp.contains("icq.im/") {
|
||||
stamp.rsplit('/').next().unwrap().into()
|
||||
} else {
|
||||
stamp
|
||||
};
|
||||
|
||||
protocol::join_chat(&session, &stamp)
|
||||
.await
|
||||
.map_err(|e| format!("Failed to join chat: {:?}", e))?;
|
||||
let chat_info_response = protocol::get_chat_info(&session, &stamp)
|
||||
.await
|
||||
.map_err(|e| format!("Failed to get chat info: {:?}", e))?;
|
||||
|
||||
self.tx
|
||||
.handle_proxy(&message.handle)
|
||||
.exec_no_return(move |plugin, protocol_data| {
|
||||
let chat_info = ChatInfo::from(chat_info_response);
|
||||
let partial_info = chat_info.as_partial();
|
||||
let connection = &mut protocol_data.connection;
|
||||
plugin.chat_joined(connection, &partial_info);
|
||||
plugin.conversation_joined(connection, &partial_info);
|
||||
plugin.load_chat_info(connection, &chat_info);
|
||||
})
|
||||
.await;
|
||||
*/
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn get_history(&mut self, _get_history_message: GetHistoryMessage) -> Result<(), String> {
|
||||
/*
|
||||
let session = {
|
||||
get_history_message
|
||||
.protocol_data
|
||||
.session
|
||||
.read()
|
||||
.await
|
||||
.clone()
|
||||
.unwrap()
|
||||
};
|
||||
let sn = &get_history_message.message_data.sn;
|
||||
let from_msg_id = &get_history_message.message_data.from_msg_id;
|
||||
let count = get_history_message.message_data.count;
|
||||
|
||||
let history = protocol::get_history(&session, sn, from_msg_id, count)
|
||||
.await
|
||||
.map_err(|e| format!("Failed to get history: {:?}", e))?;
|
||||
|
||||
super::poller::process_hist_dlg_state_messages(
|
||||
self.tx.clone(),
|
||||
session,
|
||||
get_history_message.handle,
|
||||
sn,
|
||||
&history.persons,
|
||||
None,
|
||||
&history.messages,
|
||||
)
|
||||
.await;
|
||||
*/
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn send_msg(&mut self, message: SendMsgMessage) -> Result<(), String> {
|
||||
log::info!("send_msg({:?})", message);
|
||||
/*
|
||||
let to_sn = &message.message_data.to_sn;
|
||||
let message_body = &message.message_data.message;
|
||||
let session = { message.protocol_data.session.read().await.clone().unwrap() };
|
||||
let _msg_info = protocol::send_im(&session, to_sn, message_body)
|
||||
.await
|
||||
.map_err(|e| format!("Failed to send msg: {:?}", e))?;
|
||||
*/
|
||||
Ok(())
|
||||
}
|
||||
}
|
800
src/lib.rs
Normal file
800
src/lib.rs
Normal file
@@ -0,0 +1,800 @@
|
||||
extern crate async_std;
|
||||
extern crate deltachat;
|
||||
extern crate lazy_static;
|
||||
extern crate log;
|
||||
extern crate openssl;
|
||||
extern crate purple_rs as purple;
|
||||
|
||||
use async_std::sync::Arc;
|
||||
// use chat_info::ChatInfo; //PartialChatInfo, ChatInfoVersion
|
||||
use lazy_static::lazy_static;
|
||||
use messages::{AccountInfo, DeltaSystemHandle, PurpleMessage, SystemMessage};
|
||||
use purple::*;
|
||||
//use std::cell::RefCell;
|
||||
use std::ffi::{CStr, CString};
|
||||
//use std::io::Read;
|
||||
//use std::rc::Rc;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
|
||||
pub mod chat_info;
|
||||
pub mod delta;
|
||||
pub mod logging;
|
||||
pub mod messages;
|
||||
|
||||
pub mod status {
|
||||
use lazy_static::lazy_static;
|
||||
use std::ffi::CString;
|
||||
lazy_static! {
|
||||
pub static ref ONLINE_ID: CString = CString::new("online").unwrap();
|
||||
pub static ref ONLINE_NAME: CString = CString::new("Online").unwrap();
|
||||
pub static ref OFFLINE_ID: CString = CString::new("offline").unwrap();
|
||||
pub static ref OFFLINE_NAME: CString = CString::new("Offline").unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
static ref ICON_FILE: CString = CString::new("delta").unwrap();
|
||||
}
|
||||
|
||||
mod blist_node {
|
||||
pub const LAST_SEEN_TIMESTAMP: &str = "last_seen_timestamp";
|
||||
}
|
||||
|
||||
mod commands {
|
||||
pub const IMEX: &str = "imex";
|
||||
}
|
||||
|
||||
pub mod chat_states {
|
||||
pub const JOINED: &str = "joined";
|
||||
}
|
||||
|
||||
pub mod conv_data {
|
||||
use super::HistoryInfo;
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
|
||||
pub const CHAT_INFO: &str = "chat_info";
|
||||
pub const HISTORY_INFO: &str = "history_info";
|
||||
pub type HistoryInfoType = Rc<RefCell<HistoryInfo>>;
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct HistoryInfo {
|
||||
pub oldest_message_id: Option<String>,
|
||||
pub oldest_message_timestamp: Option<i64>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct MsgInfo {
|
||||
pub chat_sn: String,
|
||||
pub author_sn: String,
|
||||
pub author_friendly: String,
|
||||
pub text: String,
|
||||
pub time: i64,
|
||||
pub message_id: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct AccountData {
|
||||
email_address: String,
|
||||
display_name: String,
|
||||
imap_host: String,
|
||||
imap_port: String,
|
||||
imap_user: String,
|
||||
imap_pass: String,
|
||||
smtp_host: String,
|
||||
smtp_port: String,
|
||||
smtp_user: String,
|
||||
smtp_pass: String,
|
||||
bcc_self: bool,
|
||||
|
||||
// Not exposed: server_flags, selfstatus, e2ee_enabled
|
||||
session_closed: AtomicBool,
|
||||
}
|
||||
|
||||
impl Drop for AccountData {
|
||||
fn drop(&mut self) {
|
||||
log::info!("AccountData dropped");
|
||||
}
|
||||
}
|
||||
|
||||
pub type AccountDataBox = Arc<AccountData>;
|
||||
pub type Handle = purple::Handle<AccountDataBox>;
|
||||
pub type ProtocolData = purple::ProtocolData<AccountDataBox>;
|
||||
|
||||
pub struct PurpleDelta {
|
||||
system: DeltaSystemHandle,
|
||||
connections: purple::Connections<AccountDataBox>,
|
||||
imex_command_handle: Option<PurpleCmdId>,
|
||||
}
|
||||
|
||||
impl purple::PrplPlugin for PurpleDelta {
|
||||
type Plugin = Self;
|
||||
|
||||
fn new() -> Self {
|
||||
logging::init(log::LevelFilter::Debug).expect("Failed to initialize logging");
|
||||
let system = delta::system::spawn();
|
||||
Self {
|
||||
system,
|
||||
imex_command_handle: None,
|
||||
connections: purple::Connections::new(),
|
||||
}
|
||||
}
|
||||
fn register(&self, context: RegisterContext<Self>) -> RegisterContext<Self> {
|
||||
println!("OpenSSL version: {}", openssl::version::version());
|
||||
|
||||
let info = purple::PrplInfo {
|
||||
id: "prpl-delta".into(),
|
||||
name: "Delta Chat".into(),
|
||||
version: "0.1.0".into(),
|
||||
summary: "Delta Chat is an email-based instant messaging solution".into(),
|
||||
description: "See https://delta.chat for more information".into(),
|
||||
author: "Nick Thomas <delta@ur.gs>".into(),
|
||||
homepage: "https://code.ur.gs/lupine/purple-plugin-delta".into(),
|
||||
};
|
||||
|
||||
context
|
||||
.with_info(info)
|
||||
.with_password()
|
||||
.with_string_option("Display Name".into(), "displayname".into(), "".into())
|
||||
.with_string_option("IMAP server host".into(), "mail_server".into(), "".into())
|
||||
.with_string_option("IMAP server port".into(), "mail_port".into(), "".into())
|
||||
.with_string_option("IMAP server username".into(), "mail_user".into(), "".into())
|
||||
// Password is account password
|
||||
.with_string_option("SMTP server host".into(), "send_server".into(), "".into())
|
||||
.with_string_option("SMTP server port".into(), "send_port".into(), "".into())
|
||||
.with_string_option("SMTP server username".into(), "send_user".into(), "".into())
|
||||
.with_password_option("SMTP server password".into(), "send_pw".into(), "".into())
|
||||
.with_bool_option("Copy messages to self".into(), "bcc_self".into(), false)
|
||||
.enable_login()
|
||||
.enable_load()
|
||||
.enable_close()
|
||||
//.enable_chat_info()
|
||||
//.enable_chat_info_defaults()
|
||||
//.enable_join_chat()
|
||||
//.enable_chat_leave()
|
||||
//.enable_send_im()
|
||||
//.enable_chat_send()
|
||||
//.enable_convo_closed()
|
||||
//.enable_get_chat_name()
|
||||
.enable_list_icon()
|
||||
.enable_status_types()
|
||||
}
|
||||
}
|
||||
|
||||
impl purple::LoginHandler for PurpleDelta {
|
||||
fn login(&mut self, account: &mut Account) {
|
||||
let email_address = account.get_username().unwrap().into();
|
||||
let display_name = account.get_string("displayname", "");
|
||||
|
||||
let imap_host = account.get_string("mail_server", "");
|
||||
let imap_port = account.get_string("mail_port", "");
|
||||
let imap_user = account.get_string("mail_user", "");
|
||||
let imap_pass = account.get_password().unwrap().into();
|
||||
|
||||
let smtp_host = account.get_string("send_server", "");
|
||||
let smtp_port = account.get_string("send_port", "");
|
||||
let smtp_user = account.get_string("send_user", "");
|
||||
let smtp_pass = account.get_string("send_pw", "");
|
||||
|
||||
let bcc_self = account.get_bool("bcc_self", false);
|
||||
|
||||
let protocol_data: AccountDataBox = Arc::new(AccountData {
|
||||
email_address,
|
||||
display_name,
|
||||
|
||||
imap_host,
|
||||
imap_port,
|
||||
imap_user,
|
||||
imap_pass,
|
||||
|
||||
smtp_host,
|
||||
smtp_port,
|
||||
smtp_user,
|
||||
smtp_pass,
|
||||
|
||||
bcc_self,
|
||||
|
||||
session_closed: AtomicBool::new(false),
|
||||
});
|
||||
|
||||
// SAFETY:
|
||||
// Safe as long as we remove the account in "close".
|
||||
unsafe {
|
||||
self.connections
|
||||
.add(account.get_connection().unwrap(), protocol_data.clone())
|
||||
};
|
||||
|
||||
self.system
|
||||
.tx
|
||||
.try_send(PurpleMessage::Login(AccountInfo {
|
||||
handle: Handle::from(&mut *account),
|
||||
protocol_data,
|
||||
}))
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
impl purple::CloseHandler for PurpleDelta {
|
||||
fn close(&mut self, connection: &mut Connection) {
|
||||
let handle = Handle::from(&mut *connection);
|
||||
match self.connections.get(&handle) {
|
||||
Some(protocol_data) => {
|
||||
protocol_data
|
||||
.data
|
||||
.session_closed
|
||||
.store(true, Ordering::Relaxed);
|
||||
|
||||
self.connections.remove(*connection);
|
||||
}
|
||||
None => {
|
||||
log::error!("Tried closing a closed connection");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
impl purple::StatusTypeHandler for PurpleDelta {
|
||||
fn status_types(_account: &mut Account) -> Vec<StatusType> {
|
||||
vec![
|
||||
StatusType::new(
|
||||
PurpleStatusPrimitive::PURPLE_STATUS_AVAILABLE,
|
||||
Some(&status::ONLINE_ID),
|
||||
Some(&status::ONLINE_NAME),
|
||||
true,
|
||||
),
|
||||
StatusType::new(
|
||||
PurpleStatusPrimitive::PURPLE_STATUS_OFFLINE,
|
||||
Some(&status::OFFLINE_ID),
|
||||
Some(&status::OFFLINE_NAME),
|
||||
true,
|
||||
),
|
||||
]
|
||||
}
|
||||
}
|
||||
impl purple::LoadHandler for PurpleDelta {
|
||||
fn load(&mut self, _plugin: &purple::Plugin) -> bool {
|
||||
logging::set_thread_logger(logging::PurpleDebugLogger);
|
||||
|
||||
self.imex_command_handle =
|
||||
Some(self.enable_command(commands::IMEX, "w", "imex <code>"));
|
||||
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
impl purple::ListIconHandler for PurpleDelta {
|
||||
fn list_icon(_account: &mut Account) -> &'static CStr {
|
||||
&ICON_FILE
|
||||
}
|
||||
}
|
||||
/*
|
||||
impl purple::ChatInfoHandler for PurpleDelta {
|
||||
fn chat_info(&mut self, _connection: &mut Connection) -> Vec<purple::prpl::ChatEntry> {
|
||||
vec![purple::prpl::ChatEntry {
|
||||
label: &chat_info::SN_NAME,
|
||||
identifier: &chat_info::SN,
|
||||
required: true,
|
||||
is_int: false,
|
||||
min: 0,
|
||||
max: 0,
|
||||
secret: false,
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
impl purple::ChatInfoDefaultsHandler for PurpleDelta {
|
||||
fn chat_info_defaults(
|
||||
&mut self,
|
||||
_connection: &mut Connection,
|
||||
chat_name: Option<&str>,
|
||||
) -> purple::StrHashTable {
|
||||
let mut defaults = purple::StrHashTable::default();
|
||||
defaults.insert(chat_info::SN.as_c_str(), chat_name.unwrap_or(""));
|
||||
defaults
|
||||
}
|
||||
}
|
||||
|
||||
impl purple::JoinChatHandler for PurpleDelta {
|
||||
fn join_chat(&mut self, connection: &mut Connection, data: Option<&mut StrHashTable>) {
|
||||
let data = match data {
|
||||
Some(data) => data,
|
||||
None => {
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let stamp = match Self::get_chat_name(Some(data)) {
|
||||
Some(stamp) => stamp,
|
||||
None => {
|
||||
log::error!("No chat name provided");
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
log::info!("Joining {}", stamp);
|
||||
|
||||
let handle = Handle::from(&mut *connection);
|
||||
let protocol_data = self
|
||||
.connections
|
||||
.get(&handle)
|
||||
.expect("Tried joining chat on closed connection");
|
||||
|
||||
if let Some(chat_states::JOINED) = data.lookup(&chat_info::STATE) {
|
||||
match PartialChatInfo::from_hashtable(data) {
|
||||
Some(chat_info) => {
|
||||
self.conversation_joined(connection, &chat_info);
|
||||
/*
|
||||
self.system
|
||||
.tx
|
||||
.try_send(PurpleMessage::get_chat_info(
|
||||
handle,
|
||||
protocol_data.data.clone(),
|
||||
chat_info.sn,
|
||||
))
|
||||
.unwrap(); */
|
||||
return;
|
||||
}
|
||||
None => {
|
||||
log::error!("Unable to load chat info");
|
||||
}
|
||||
}
|
||||
}
|
||||
/*
|
||||
self.system
|
||||
.tx
|
||||
.try_send(PurpleMessage::join_chat(
|
||||
handle,
|
||||
protocol_data.data.clone(),
|
||||
stamp,
|
||||
))
|
||||
.unwrap() */
|
||||
}
|
||||
}
|
||||
|
||||
impl purple::ChatLeaveHandler for PurpleDelta {
|
||||
fn chat_leave(&mut self, connection: &mut Connection, id: i32) {
|
||||
log::info!("Chat leave: {}", id);
|
||||
match Conversation::find(connection, id) {
|
||||
Some(mut conversation) => {
|
||||
unsafe { conversation.remove_data::<ChatInfo>(conv_data::CHAT_INFO) };
|
||||
}
|
||||
None => {
|
||||
log::warn!("Leaving chat without conversation");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl purple::ConvoClosedHandler for PurpleDelta {
|
||||
fn convo_closed(&mut self, _connection: &mut Connection, who: Option<&str>) {
|
||||
log::info!("Convo closed: {:?}", who)
|
||||
}
|
||||
}
|
||||
|
||||
impl purple::GetChatNameHandler for PurpleDelta {
|
||||
fn get_chat_name(data: Option<&mut purple::StrHashTable>) -> Option<String> {
|
||||
data.and_then(|h| h.lookup(chat_info::SN.as_c_str()).map(Into::into))
|
||||
}
|
||||
}
|
||||
|
||||
impl purple::SendIMHandler for PurpleDelta {
|
||||
fn send_im(
|
||||
&mut self,
|
||||
_connection: &mut Connection,
|
||||
_who: &str,
|
||||
_message: &str,
|
||||
_flags: PurpleMessageFlags,
|
||||
) -> i32 {
|
||||
log::warn!("SendIM is not implemented");
|
||||
-1
|
||||
}
|
||||
}
|
||||
|
||||
impl purple::ChatSendHandler for PurpleDelta {
|
||||
fn chat_send(
|
||||
&mut self,
|
||||
connection: &mut Connection,
|
||||
id: i32,
|
||||
message: &str,
|
||||
flags: PurpleMessageFlags,
|
||||
) -> i32 {
|
||||
log::info!("{}: {} [{:?}]", id, message, flags);
|
||||
let mut conversation = match Conversation::find(connection, id) {
|
||||
Some(c) => c,
|
||||
None => {
|
||||
log::error!("Conversation not found");
|
||||
return -1;
|
||||
}
|
||||
};
|
||||
|
||||
let sn = match unsafe { conversation.get_data::<ChatInfo>(conv_data::CHAT_INFO) } {
|
||||
Some(info) => info.sn.clone(),
|
||||
None => {
|
||||
log::error!("SN not found");
|
||||
return -1;
|
||||
}
|
||||
};
|
||||
|
||||
let handle = Handle::from(&mut *connection);
|
||||
let protocol_data = self.connections.get(&handle).expect("Connection closed");
|
||||
/*
|
||||
self.system
|
||||
.tx
|
||||
.try_send(PurpleMessage::send_msg(
|
||||
handle,
|
||||
protocol_data.data.clone(),
|
||||
sn,
|
||||
message.into(),
|
||||
))
|
||||
.unwrap(); */
|
||||
1
|
||||
}
|
||||
}
|
||||
*/
|
||||
impl purple::CommandHandler for PurpleDelta {
|
||||
fn command(
|
||||
&mut self,
|
||||
conversation: &mut Conversation,
|
||||
command: &str,
|
||||
args: &[&str],
|
||||
) -> PurpleCmdRet {
|
||||
log::debug!(
|
||||
"command: conv={} cmd={} args={:?}",
|
||||
conversation.get_title().unwrap_or("unknown"),
|
||||
command,
|
||||
args
|
||||
);
|
||||
match command {
|
||||
commands::IMEX => self.command_imex(conversation, args),
|
||||
_ => {
|
||||
log::error!("Unknown command: {}", command);
|
||||
PurpleCmdRet::PURPLE_CMD_RET_FAILED
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PurpleDelta {
|
||||
fn command_imex(&mut self, _conversation: &mut Conversation, args: &[&str]) -> PurpleCmdRet {
|
||||
log::debug!("command_imex");
|
||||
|
||||
if args.len() != 1 {
|
||||
log::error!(
|
||||
"command_imex: Unsupported number of args. Got {}",
|
||||
args.len()
|
||||
);
|
||||
return PurpleCmdRet::PURPLE_CMD_RET_FAILED;
|
||||
}
|
||||
/*
|
||||
let count = {
|
||||
let input = match args[0].parse::<u32>() {
|
||||
Ok(count) => count,
|
||||
Err(_) => {
|
||||
log::error!("command_history: Could not parse count: {}", args[0]);
|
||||
return PurpleCmdRet::PURPLE_CMD_RET_FAILED;
|
||||
}
|
||||
};
|
||||
0 - input as i32
|
||||
};
|
||||
|
||||
let sn = match conversation.get_name() {
|
||||
Some(name) => name.to_string(),
|
||||
None => {
|
||||
log::error!("command_history: SN not found");
|
||||
return PurpleCmdRet::PURPLE_CMD_RET_FAILED;
|
||||
}
|
||||
};
|
||||
|
||||
let from_msg_id = {
|
||||
match unsafe {
|
||||
conversation.get_data::<conv_data::HistoryInfoType>(conv_data::HISTORY_INFO)
|
||||
} {
|
||||
Some(history_info) => {
|
||||
let history_info = history_info.borrow_mut();
|
||||
match &history_info.oldest_message_id {
|
||||
Some(oldest_message_id) => oldest_message_id.clone(),
|
||||
None => {
|
||||
return PurpleCmdRet::PURPLE_CMD_RET_FAILED;
|
||||
}
|
||||
}
|
||||
}
|
||||
None => {
|
||||
log::error!("command_history: Can't find message id");
|
||||
return PurpleCmdRet::PURPLE_CMD_RET_FAILED;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let handle = Handle::from(&mut conversation.get_connection());
|
||||
|
||||
let protocol_data = self
|
||||
.connections
|
||||
.get(&handle)
|
||||
.expect("Tried joining chat on closed connection");
|
||||
|
||||
self.system
|
||||
.tx
|
||||
.try_send(PurpleMessage::fetch_history(
|
||||
handle,
|
||||
protocol_data.data.clone(),
|
||||
sn,
|
||||
from_msg_id,
|
||||
count,
|
||||
))
|
||||
.unwrap();
|
||||
*/
|
||||
PurpleCmdRet::PURPLE_CMD_RET_OK
|
||||
}
|
||||
|
||||
fn process_message(&mut self, message: SystemMessage) {
|
||||
log::info!("received system message");
|
||||
match message {
|
||||
SystemMessage::ExecAccount {
|
||||
handle: _,
|
||||
function: _,
|
||||
} => {
|
||||
/*
|
||||
self.connections
|
||||
.get(handle)
|
||||
.map(|protocol_data| function(&mut protocol_data.account))
|
||||
.or_else(|| {
|
||||
log::warn!("The account connection has been closed");
|
||||
None
|
||||
});
|
||||
*/
|
||||
}
|
||||
SystemMessage::ExecConnection {
|
||||
handle: _,
|
||||
function: _,
|
||||
} => {
|
||||
/*
|
||||
self.connections
|
||||
.get(handle)
|
||||
.map(|protocol_data| function(&mut protocol_data.connection))
|
||||
.or_else(|| {
|
||||
log::warn!("The account connection has been closed");
|
||||
None
|
||||
});
|
||||
*/
|
||||
}
|
||||
SystemMessage::ExecHandle {
|
||||
handle: _,
|
||||
function: _,
|
||||
} => {
|
||||
/*
|
||||
self.connections
|
||||
.get(handle)
|
||||
.map(|mut protocol_data| function(self, &mut protocol_data))
|
||||
.or_else(|| {
|
||||
log::warn!("The account connection has been closed");
|
||||
None
|
||||
});
|
||||
*/
|
||||
}
|
||||
SystemMessage::FlushLogs => logging::flush(),
|
||||
}
|
||||
}
|
||||
/*
|
||||
pub fn serv_got_chat_in(&mut self, connection: &mut Connection, msg_info: MsgInfo) {
|
||||
match purple::Chat::find(&mut connection.get_account(), &msg_info.chat_sn) {
|
||||
Some(mut chat) => {
|
||||
// Get the chat and the last seen timestamp.
|
||||
let mut node = chat.as_blist_node();
|
||||
let last_timestamp: i64 = node
|
||||
.get_string(&blist_node::LAST_SEEN_TIMESTAMP)
|
||||
.and_then(|t| t.parse::<i64>().ok())
|
||||
.unwrap_or(0);
|
||||
let new_timestamp = msg_info.time;
|
||||
|
||||
// Only trigger conversation_joined if this is a new message.
|
||||
let conversation = {
|
||||
if new_timestamp > last_timestamp {
|
||||
node.set_string(
|
||||
&blist_node::LAST_SEEN_TIMESTAMP,
|
||||
&new_timestamp.to_string(),
|
||||
);
|
||||
Some(self.conversation_joined(
|
||||
connection,
|
||||
&PartialChatInfo {
|
||||
sn: msg_info.chat_sn.clone(),
|
||||
title: msg_info.chat_sn.clone(),
|
||||
..Default::default()
|
||||
},
|
||||
))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
};
|
||||
|
||||
// Get the conversation and set the oldest *displayed* messageId.
|
||||
// This is the oldest message that the user can see in the chat window.
|
||||
//
|
||||
// If there is no conversation yet, that is okay. It means that we haven't
|
||||
// seen new messages yet.
|
||||
if let Some(mut conversation) = conversation {
|
||||
let history_info = {
|
||||
match unsafe {
|
||||
conversation
|
||||
.get_data::<conv_data::HistoryInfoType>(conv_data::HISTORY_INFO)
|
||||
} {
|
||||
Some(history_info) => history_info.clone(),
|
||||
None => {
|
||||
let history_info = Rc::new(RefCell::new(HistoryInfo {
|
||||
oldest_message_id: None,
|
||||
oldest_message_timestamp: None,
|
||||
}));
|
||||
unsafe {
|
||||
conversation.set_data::<conv_data::HistoryInfoType>(
|
||||
conv_data::HISTORY_INFO,
|
||||
history_info.clone(),
|
||||
)
|
||||
};
|
||||
history_info
|
||||
}
|
||||
}
|
||||
};
|
||||
let mut history_info = history_info.borrow_mut();
|
||||
|
||||
match history_info.oldest_message_timestamp {
|
||||
None => {
|
||||
history_info.oldest_message_id = Some(msg_info.message_id.clone());
|
||||
history_info.oldest_message_timestamp = Some(msg_info.time);
|
||||
}
|
||||
Some(existing_timestamp) => {
|
||||
if msg_info.time < existing_timestamp {
|
||||
history_info.oldest_message_id = Some(msg_info.message_id.clone());
|
||||
history_info.oldest_message_timestamp = Some(msg_info.time);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
None => {
|
||||
// Don't log errors for DMs because they are not yet supported.
|
||||
// It happens all the time.
|
||||
if msg_info.chat_sn.ends_with("@chat.agent") {
|
||||
log::error!("Got message for unknown chat {}", msg_info.chat_sn);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
connection.serv_got_chat_in(msg_info);
|
||||
}
|
||||
|
||||
pub fn chat_joined(&mut self, connection: &mut Connection, info: &PartialChatInfo) {
|
||||
log::info!("chat joined: {}", info.sn);
|
||||
if info.sn.ends_with("@chat.agent") {
|
||||
self.group_chat_joined(connection, info)
|
||||
} else {
|
||||
todo!()
|
||||
};
|
||||
}
|
||||
|
||||
fn group_chat_joined(
|
||||
&mut self,
|
||||
connection: &mut Connection,
|
||||
info: &PartialChatInfo,
|
||||
) -> purple::Chat {
|
||||
let mut account = connection.get_account();
|
||||
match purple::Chat::find(&mut account, &info.sn) {
|
||||
Some(mut chat) => {
|
||||
// The chat already exists.
|
||||
|
||||
// Should we replace the blist group?
|
||||
if let Some(info_group) = &info.group {
|
||||
let should_replace_group = {
|
||||
match chat.get_group() {
|
||||
Some(mut chat_group) => !chat_group.get_name().eq(info_group),
|
||||
None => true,
|
||||
}
|
||||
};
|
||||
if should_replace_group {
|
||||
chat.add_to_blist(&mut self.get_or_create_group(Some(&info_group)), None);
|
||||
}
|
||||
}
|
||||
|
||||
// Replace the alias
|
||||
chat.set_alias(&info.title);
|
||||
chat
|
||||
}
|
||||
None => {
|
||||
let mut components = info.as_hashtable();
|
||||
components.insert(&chat_info::STATE, chat_states::JOINED);
|
||||
let mut chat = purple::Chat::new(&mut account, &info.title, components);
|
||||
chat.add_to_blist(&mut self.get_or_create_group(info.group.as_deref()), None);
|
||||
chat
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn get_or_create_group(&mut self, name: Option<&str>) -> purple::Group {
|
||||
let name = name.unwrap_or("ICQ");
|
||||
Group::find(name).unwrap_or_else(|| {
|
||||
let mut group = purple::Group::new(name);
|
||||
group.add_to_blist(None);
|
||||
group
|
||||
})
|
||||
}
|
||||
|
||||
pub fn conversation_joined(
|
||||
&mut self,
|
||||
connection: &mut Connection,
|
||||
info: &PartialChatInfo,
|
||||
) -> Conversation {
|
||||
match connection.get_account().find_chat_conversation(&info.sn) {
|
||||
Some(mut conversation) => {
|
||||
if conversation.get_chat_data().unwrap().has_left() {
|
||||
log::error!("Trying to join left conversation");
|
||||
} else {
|
||||
conversation.present();
|
||||
}
|
||||
conversation
|
||||
}
|
||||
None => {
|
||||
let mut conversation = connection.serv_got_joined_chat(&info.sn).unwrap();
|
||||
conversation.set_title(&info.title);
|
||||
conversation
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn check_chat_info(
|
||||
&mut self,
|
||||
connection: &mut Connection,
|
||||
sn: &str,
|
||||
version: &ChatInfoVersion,
|
||||
) {
|
||||
match connection.get_account().find_chat_conversation(&sn) {
|
||||
Some(mut conversation) => {
|
||||
let chat_info = unsafe { conversation.get_data::<ChatInfo>(conv_data::CHAT_INFO) };
|
||||
if chat_info
|
||||
.map(|chat_info| chat_info.need_update(version))
|
||||
.unwrap_or(true)
|
||||
{
|
||||
log::info!("Fetching chat info: {}", sn);
|
||||
let handle = Handle::from(&mut *connection);
|
||||
let protocol_data = self
|
||||
.connections
|
||||
.get(&handle)
|
||||
.expect("Tried get chat info on closed connection");
|
||||
self.system
|
||||
.tx
|
||||
.try_send(PurpleMessage::get_chat_info(
|
||||
handle,
|
||||
protocol_data.data.clone(),
|
||||
sn.to_string(),
|
||||
))
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
None => {
|
||||
log::warn!("Checking chat info for no conversation");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn load_chat_info(&mut self, connection: &mut Connection, info: &ChatInfo) {
|
||||
log::debug!("loading chat info: {:?}", info);
|
||||
match connection.get_account().find_chat_conversation(&info.sn) {
|
||||
Some(mut conversation) => {
|
||||
conversation.set_title(&info.title);
|
||||
let mut chat_conversation = conversation.get_chat_data().unwrap();
|
||||
unsafe { conversation.set_data(conv_data::CHAT_INFO, info.clone()) };
|
||||
|
||||
chat_conversation.clear_users();
|
||||
for member in &info.members {
|
||||
chat_conversation.add_user(&member.sn, "", member.role.as_flags(), false);
|
||||
}
|
||||
|
||||
if let Some(about) = &info.about {
|
||||
chat_conversation.set_topic("unknown", about);
|
||||
}
|
||||
}
|
||||
None => {
|
||||
log::warn!("Loaded chat info for no conversation");
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
purple_prpl_plugin!(PurpleDelta);
|
120
src/logging.rs
Normal file
120
src/logging.rs
Normal file
@@ -0,0 +1,120 @@
|
||||
// This is a copy of https://github.com/Flared/purple-icq/blob/master/src/logging.rs
|
||||
|
||||
use crate::messages::{FdSender, SystemMessage};
|
||||
use crate::purple;
|
||||
use std::cell::RefCell;
|
||||
use std::sync::Mutex;
|
||||
|
||||
std::thread_local! {
|
||||
pub static LOGGER: RefCell<Option<Box<dyn log::Log>>> = RefCell::new(None);
|
||||
}
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
static ref PURPLE_BUFFER: Mutex<Vec<(String, log::Level, String)>> = Default::default();
|
||||
}
|
||||
|
||||
static TLS_LOGGER: ThreadLocalLogger = ThreadLocalLogger;
|
||||
|
||||
pub struct ThreadLocalLogger;
|
||||
|
||||
impl log::Log for ThreadLocalLogger {
|
||||
fn enabled(&self, _metadata: &log::Metadata) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn log(&self, record: &log::Record) {
|
||||
LOGGER.with(|cell| {
|
||||
if let Some(ref logger) = cell.borrow().as_ref() {
|
||||
logger.log(record);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn flush(&self) {
|
||||
LOGGER.with(|cell| {
|
||||
if let Some(ref logger) = cell.borrow().as_ref() {
|
||||
logger.flush()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub struct PurpleDebugLogger;
|
||||
|
||||
impl log::Log for PurpleDebugLogger {
|
||||
fn enabled(&self, metadata: &log::Metadata) -> bool {
|
||||
metadata.level() < log::Level::Debug
|
||||
}
|
||||
|
||||
fn log(&self, record: &log::Record) {
|
||||
let purple_level = match record.level() {
|
||||
log::Level::Error => purple::PurpleDebugLevel::PURPLE_DEBUG_ERROR,
|
||||
log::Level::Warn => purple::PurpleDebugLevel::PURPLE_DEBUG_WARNING,
|
||||
log::Level::Info => purple::PurpleDebugLevel::PURPLE_DEBUG_INFO,
|
||||
_ => purple::PurpleDebugLevel::PURPLE_DEBUG_MISC,
|
||||
};
|
||||
|
||||
let target = if !record.target().is_empty() {
|
||||
record.target()
|
||||
} else {
|
||||
record.module_path().unwrap_or_default()
|
||||
};
|
||||
let line = format!("[{}] {}\n", target, record.args());
|
||||
purple::debug(purple_level, "", &line);
|
||||
}
|
||||
|
||||
fn flush(&self) {
|
||||
let buffer = {
|
||||
match PURPLE_BUFFER.lock() {
|
||||
Ok(mut buffer) => buffer.split_off(0),
|
||||
Err(_) => return,
|
||||
}
|
||||
};
|
||||
for (target, level, message) in buffer {
|
||||
log::log!(target: &target, level, "{}", message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct RemoteLogger(pub FdSender<SystemMessage>);
|
||||
|
||||
impl log::Log for RemoteLogger {
|
||||
fn enabled(&self, metadata: &log::Metadata) -> bool {
|
||||
metadata.level() < log::Level::Debug
|
||||
}
|
||||
|
||||
fn log(&self, record: &log::Record) {
|
||||
let target = if !record.target().is_empty() {
|
||||
record.target()
|
||||
} else {
|
||||
record.module_path().unwrap_or_default()
|
||||
};
|
||||
|
||||
if let Ok(mut buffer) = PURPLE_BUFFER.lock() {
|
||||
buffer.push((target.into(), record.level(), record.args().to_string()));
|
||||
}
|
||||
}
|
||||
|
||||
fn flush(&self) {
|
||||
self.0.clone().try_send(SystemMessage::FlushLogs);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn init(level: log::LevelFilter) -> Result<(), log::SetLoggerError> {
|
||||
log::set_logger(&TLS_LOGGER).map(|()| log::set_max_level(level))
|
||||
}
|
||||
|
||||
pub fn set_thread_logger<T>(logger: T)
|
||||
where
|
||||
T: log::Log + 'static,
|
||||
{
|
||||
LOGGER.with(|cell| *cell.borrow_mut() = Some(Box::new(logger)))
|
||||
}
|
||||
|
||||
pub fn flush() {
|
||||
LOGGER.with(|cell| {
|
||||
if let Some(ref logger) = cell.borrow().as_ref() {
|
||||
logger.flush();
|
||||
}
|
||||
})
|
||||
}
|
101
src/messages/account_proxy.rs
Normal file
101
src/messages/account_proxy.rs
Normal file
@@ -0,0 +1,101 @@
|
||||
use super::{FdSender, SystemMessage};
|
||||
use crate::Handle;
|
||||
use async_std::channel;
|
||||
use purple::{account, Account};
|
||||
|
||||
pub struct AccountProxy<'a> {
|
||||
pub handle: Handle,
|
||||
pub sender: &'a mut FdSender<SystemMessage>,
|
||||
}
|
||||
impl<'a> AccountProxy<'a> {
|
||||
pub async fn exec<F, T>(&mut self, f: F) -> Option<T>
|
||||
where
|
||||
F: FnOnce(&mut Account) -> T,
|
||||
F: Send + 'static,
|
||||
T: Send + 'static,
|
||||
{
|
||||
let (tx, rx) = channel::bounded(1);
|
||||
self.exec_no_return(move |account| {
|
||||
if let Err(error) = tx.try_send(f(account)) {
|
||||
log::error!("Failed to send result: {:?}", error);
|
||||
}
|
||||
})
|
||||
.await;
|
||||
rx.recv().await.ok().or_else(|| {
|
||||
log::error!("Failed to receive result");
|
||||
None
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn exec_no_return<F>(&mut self, f: F)
|
||||
where
|
||||
F: FnOnce(&mut Account),
|
||||
F: Send + 'static,
|
||||
{
|
||||
self.sender
|
||||
.send(SystemMessage::ExecAccount {
|
||||
handle: self.handle.clone(),
|
||||
function: Box::new(f),
|
||||
})
|
||||
.await;
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub async fn request_input(
|
||||
&mut self,
|
||||
title: Option<String>,
|
||||
primary: Option<String>,
|
||||
secondary: Option<String>,
|
||||
default_value: Option<String>,
|
||||
multiline: bool,
|
||||
masked: bool,
|
||||
hint: Option<String>,
|
||||
ok_text: String,
|
||||
cancel_text: String,
|
||||
who: Option<String>,
|
||||
) -> Option<String> {
|
||||
let (tx, rx) = channel::bounded(1);
|
||||
self.exec_no_return(move |account| {
|
||||
account.request_input(
|
||||
title.as_deref(),
|
||||
primary.as_deref(),
|
||||
secondary.as_deref(),
|
||||
default_value.as_deref(),
|
||||
multiline,
|
||||
masked,
|
||||
hint.as_deref(),
|
||||
&ok_text,
|
||||
&cancel_text,
|
||||
move |input_value| {
|
||||
if let Err(error) = tx.try_send(input_value.map(|v| v.into_owned())) {
|
||||
log::error!("Failed to send result: {:?}", error);
|
||||
}
|
||||
},
|
||||
who.as_deref(),
|
||||
)
|
||||
})
|
||||
.await;
|
||||
|
||||
rx.recv().await.ok().flatten()
|
||||
}
|
||||
|
||||
pub async fn is_disconnected(&mut self) -> bool {
|
||||
self.exec(move |account| account.is_disconnected())
|
||||
.await
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
pub async fn set_settings<T: 'static + serde::Serialize + Send>(
|
||||
&mut self,
|
||||
settings: T,
|
||||
) -> account::settings::Result<()> {
|
||||
self.exec(move |account| account.set_settings(&settings))
|
||||
.await
|
||||
.transpose()
|
||||
.and_then(|option| {
|
||||
option.ok_or_else(|| {
|
||||
account::settings::Error::Message("Failed to receive result".into())
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
54
src/messages/connection_proxy.rs
Normal file
54
src/messages/connection_proxy.rs
Normal file
@@ -0,0 +1,54 @@
|
||||
use super::{FdSender, SystemMessage};
|
||||
use crate::Handle;
|
||||
use async_std::channel;
|
||||
use purple::{Connection, PurpleConnectionError, PurpleConnectionState};
|
||||
|
||||
pub struct ConnectionProxy<'a> {
|
||||
pub handle: Handle,
|
||||
pub sender: &'a mut FdSender<SystemMessage>,
|
||||
}
|
||||
|
||||
impl<'a> ConnectionProxy<'a> {
|
||||
#[allow(dead_code)]
|
||||
pub async fn exec<F, T>(&mut self, f: F) -> Option<T>
|
||||
where
|
||||
F: FnOnce(&mut Connection) -> T,
|
||||
F: Send + 'static,
|
||||
T: Send + 'static,
|
||||
{
|
||||
let (tx, rx) = channel::bounded(1);
|
||||
self.exec_no_return(move |connection| {
|
||||
if let Err(error) = tx.try_send(f(connection)) {
|
||||
log::error!("Failed to send result: {:?}", error);
|
||||
}
|
||||
})
|
||||
.await;
|
||||
rx.recv().await.ok().or_else(|| {
|
||||
log::error!("Failed to receive result");
|
||||
None
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn exec_no_return<F>(&mut self, f: F)
|
||||
where
|
||||
F: FnOnce(&mut Connection),
|
||||
F: Send + 'static,
|
||||
{
|
||||
self.sender
|
||||
.send(SystemMessage::ExecConnection {
|
||||
handle: self.handle.clone(),
|
||||
function: Box::new(f),
|
||||
})
|
||||
.await;
|
||||
}
|
||||
|
||||
pub async fn set_state(&mut self, state: PurpleConnectionState) {
|
||||
self.exec_no_return(move |connection| connection.set_state(state))
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn error_reason(&mut self, reason: PurpleConnectionError, description: String) {
|
||||
self.exec_no_return(move |connection| connection.error_reason(reason, &description))
|
||||
.await
|
||||
}
|
||||
}
|
43
src/messages/handle_proxy.rs
Normal file
43
src/messages/handle_proxy.rs
Normal file
@@ -0,0 +1,43 @@
|
||||
use super::{FdSender, SystemMessage};
|
||||
use crate::{Handle, ProtocolData};
|
||||
use async_std::channel;
|
||||
|
||||
pub struct HandleProxy<'a> {
|
||||
pub handle: Handle,
|
||||
pub sender: &'a mut FdSender<SystemMessage>,
|
||||
}
|
||||
|
||||
impl<'a> HandleProxy<'a> {
|
||||
#[allow(dead_code)]
|
||||
pub async fn exec<F, T>(&mut self, f: F) -> Option<T>
|
||||
where
|
||||
F: FnOnce(&mut crate::PurpleDelta, &mut ProtocolData) -> T,
|
||||
F: Send + 'static,
|
||||
T: Send + 'static,
|
||||
{
|
||||
let (tx, rx) = channel::bounded(1);
|
||||
self.exec_no_return(move |plugin, protocol_data| {
|
||||
if let Err(error) = tx.try_send(f(plugin, protocol_data)) {
|
||||
log::error!("Failed to send result: {:?}", error);
|
||||
}
|
||||
})
|
||||
.await;
|
||||
rx.recv().await.ok().or_else(|| {
|
||||
log::error!("Failed to receive result");
|
||||
None
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn exec_no_return<F>(&mut self, f: F)
|
||||
where
|
||||
F: FnOnce(&mut crate::PurpleDelta, &mut ProtocolData),
|
||||
F: Send + 'static,
|
||||
{
|
||||
self.sender
|
||||
.send(SystemMessage::ExecHandle {
|
||||
handle: self.handle.clone(),
|
||||
function: Box::new(f),
|
||||
})
|
||||
.await;
|
||||
}
|
||||
}
|
193
src/messages/mod.rs
Normal file
193
src/messages/mod.rs
Normal file
@@ -0,0 +1,193 @@
|
||||
use self::account_proxy::AccountProxy;
|
||||
use self::connection_proxy::ConnectionProxy;
|
||||
use self::handle_proxy::HandleProxy;
|
||||
use crate::{AccountDataBox, Handle, ProtocolData, PurpleDelta};
|
||||
use async_std::channel::{Receiver, Sender};
|
||||
use purple::{Account, Connection};
|
||||
|
||||
mod account_proxy;
|
||||
mod connection_proxy;
|
||||
mod handle_proxy;
|
||||
|
||||
pub struct FdSender<T> {
|
||||
os_sender: os_pipe::PipeWriter,
|
||||
channel_sender: Sender<T>,
|
||||
}
|
||||
|
||||
impl<T> FdSender<T> {
|
||||
pub fn new(os_sender: os_pipe::PipeWriter, channel_sender: Sender<T>) -> Self {
|
||||
Self {
|
||||
os_sender,
|
||||
channel_sender,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn send(&mut self, item: T) {
|
||||
match self.channel_sender.send(item).await {
|
||||
Ok(()) => {
|
||||
use std::io::Write;
|
||||
self.os_sender.write_all(&[0]).unwrap();
|
||||
}
|
||||
Err(error) => log::error!("Failed to send message: {}", error),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn try_send(&mut self, item: T) {
|
||||
self.channel_sender.try_send(item).unwrap();
|
||||
use std::io::Write;
|
||||
self.os_sender.write_all(&[0]).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
impl FdSender<SystemMessage> {
|
||||
pub fn connection_proxy<'a>(&'a mut self, handle: &Handle) -> ConnectionProxy<'a> {
|
||||
ConnectionProxy {
|
||||
handle: handle.clone(),
|
||||
sender: self,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn account_proxy<'a>(&'a mut self, handle: &Handle) -> AccountProxy<'a> {
|
||||
AccountProxy {
|
||||
handle: handle.clone(),
|
||||
sender: self,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn handle_proxy<'a>(&'a mut self, handle: &Handle) -> HandleProxy<'a> {
|
||||
HandleProxy {
|
||||
handle: handle.clone(),
|
||||
sender: self,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Clone for FdSender<T> {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
os_sender: self.os_sender.try_clone().unwrap(),
|
||||
channel_sender: self.channel_sender.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct AccountInfo {
|
||||
pub handle: Handle,
|
||||
pub protocol_data: AccountDataBox,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct PurpleMessageWithHandle<T> {
|
||||
pub handle: Handle,
|
||||
pub protocol_data: AccountDataBox,
|
||||
pub message_data: T,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct JoinChatMessageData {
|
||||
pub stamp: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct SendMsgMessageData {
|
||||
pub to_sn: String,
|
||||
pub message: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct GetChatInfoMessageData {
|
||||
pub sn: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct GetHistoryMessageData {
|
||||
pub sn: String,
|
||||
pub from_msg_id: String,
|
||||
pub count: i32,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum PurpleMessage {
|
||||
Login(AccountInfo),
|
||||
JoinChat(JoinChatMessage),
|
||||
SendMsg(SendMsgMessage),
|
||||
GetChatInfo(GetChatInfoMessage),
|
||||
GetHistory(GetHistoryMessage),
|
||||
}
|
||||
|
||||
pub type JoinChatMessage = PurpleMessageWithHandle<JoinChatMessageData>;
|
||||
pub type GetHistoryMessage = PurpleMessageWithHandle<GetHistoryMessageData>;
|
||||
pub type SendMsgMessage = PurpleMessageWithHandle<SendMsgMessageData>;
|
||||
pub type GetChatInfoMessage = PurpleMessageWithHandle<GetChatInfoMessageData>;
|
||||
|
||||
impl PurpleMessage {
|
||||
pub fn join_chat(handle: Handle, protocol_data: AccountDataBox, stamp: String) -> Self {
|
||||
Self::JoinChat(JoinChatMessage {
|
||||
handle,
|
||||
protocol_data,
|
||||
message_data: JoinChatMessageData { stamp },
|
||||
})
|
||||
}
|
||||
|
||||
pub fn fetch_history(
|
||||
handle: Handle,
|
||||
protocol_data: AccountDataBox,
|
||||
sn: String,
|
||||
from_msg_id: String,
|
||||
count: i32,
|
||||
) -> Self {
|
||||
Self::GetHistory(GetHistoryMessage {
|
||||
handle,
|
||||
protocol_data,
|
||||
message_data: GetHistoryMessageData {
|
||||
sn,
|
||||
from_msg_id,
|
||||
count,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
pub fn send_msg(
|
||||
handle: Handle,
|
||||
protocol_data: AccountDataBox,
|
||||
to_sn: String,
|
||||
message: String,
|
||||
) -> Self {
|
||||
Self::SendMsg(SendMsgMessage {
|
||||
handle,
|
||||
protocol_data,
|
||||
message_data: SendMsgMessageData { to_sn, message },
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get_chat_info(handle: Handle, protocol_data: AccountDataBox, sn: String) -> Self {
|
||||
Self::GetChatInfo(GetChatInfoMessage {
|
||||
handle,
|
||||
protocol_data,
|
||||
message_data: GetChatInfoMessageData { sn },
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub enum SystemMessage {
|
||||
ExecAccount {
|
||||
handle: Handle,
|
||||
function: Box<dyn FnOnce(&mut Account) + Send + 'static>,
|
||||
},
|
||||
ExecConnection {
|
||||
handle: Handle,
|
||||
function: Box<dyn FnOnce(&mut Connection) + Send + 'static>,
|
||||
},
|
||||
ExecHandle {
|
||||
handle: Handle,
|
||||
function: Box<dyn FnOnce(&mut PurpleDelta, &mut ProtocolData) + Send + 'static>,
|
||||
},
|
||||
FlushLogs,
|
||||
}
|
||||
|
||||
pub struct DeltaSystemHandle {
|
||||
pub input_rx: os_pipe::PipeReader,
|
||||
pub rx: Receiver<SystemMessage>,
|
||||
pub tx: Sender<PurpleMessage>,
|
||||
}
|
Reference in New Issue
Block a user