xpc: implement rest of methods in kpcli except signals.
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
use crate::xpc::interface::SERVICE_NAME;
|
||||
use futures_util::StreamExt;
|
||||
use kordophoned::daemon::{events::Event, signals::Signal, DaemonResult};
|
||||
use kordophoned::daemon::settings::Settings;
|
||||
use std::collections::HashMap;
|
||||
use std::ffi::CString;
|
||||
use std::sync::Arc;
|
||||
@@ -144,6 +145,12 @@ fn array_from_strs(values: impl IntoIterator<Item = String>) -> Message {
|
||||
Message::Array(arr)
|
||||
}
|
||||
|
||||
fn make_ok_reply() -> Message {
|
||||
let mut reply: XpcMap = HashMap::new();
|
||||
dict_put_str(&mut reply, "type", "Ok");
|
||||
Message::Dictionary(reply)
|
||||
}
|
||||
|
||||
async fn dispatch(agent: &XpcAgent, root: &HashMap<CString, Message>) -> Message {
|
||||
// Standardized request: { method: String, arguments: Dictionary? }
|
||||
let method = match dict_get_str(root, "method").or_else(|| dict_get_str(root, "type")) {
|
||||
@@ -224,6 +231,158 @@ async fn dispatch(agent: &XpcAgent, root: &HashMap<CString, Message>) -> Message
|
||||
}
|
||||
}
|
||||
|
||||
"SyncConversationList" => {
|
||||
match agent.send_event(Event::SyncConversationList).await {
|
||||
Ok(()) => make_ok_reply(),
|
||||
Err(e) => make_error_reply("DaemonError", &format!("{}", e)),
|
||||
}
|
||||
}
|
||||
|
||||
"SyncAllConversations" => {
|
||||
match agent.send_event(Event::SyncAllConversations).await {
|
||||
Ok(()) => make_ok_reply(),
|
||||
Err(e) => make_error_reply("DaemonError", &format!("{}", e)),
|
||||
}
|
||||
}
|
||||
|
||||
"SyncConversation" => {
|
||||
let conversation_id = match get_dictionary_field(root, "arguments").and_then(|m| dict_get_str(m, "conversation_id")) {
|
||||
Some(id) => id,
|
||||
None => return make_error_reply("InvalidRequest", "Missing conversation_id"),
|
||||
};
|
||||
match agent.send_event(|r| Event::SyncConversation(conversation_id, r)).await {
|
||||
Ok(()) => make_ok_reply(),
|
||||
Err(e) => make_error_reply("DaemonError", &format!("{}", e)),
|
||||
}
|
||||
}
|
||||
|
||||
"MarkConversationAsRead" => {
|
||||
let conversation_id = match get_dictionary_field(root, "arguments").and_then(|m| dict_get_str(m, "conversation_id")) {
|
||||
Some(id) => id,
|
||||
None => return make_error_reply("InvalidRequest", "Missing conversation_id"),
|
||||
};
|
||||
match agent.send_event(|r| Event::MarkConversationAsRead(conversation_id, r)).await {
|
||||
Ok(()) => make_ok_reply(),
|
||||
Err(e) => make_error_reply("DaemonError", &format!("{}", e)),
|
||||
}
|
||||
}
|
||||
|
||||
"GetMessages" => {
|
||||
let args = match get_dictionary_field(root, "arguments") { Some(a) => a, None => return make_error_reply("InvalidRequest", "Missing arguments") };
|
||||
let conversation_id = match dict_get_str(args, "conversation_id") { Some(id) => id, None => return make_error_reply("InvalidRequest", "Missing conversation_id") };
|
||||
let last_message_id = dict_get_str(args, "last_message_id");
|
||||
match agent.send_event(|r| Event::GetMessages(conversation_id, last_message_id, r)).await {
|
||||
Ok(messages) => {
|
||||
let mut items: Vec<Message> = Vec::with_capacity(messages.len());
|
||||
for msg in messages {
|
||||
let mut m: XpcMap = HashMap::new();
|
||||
dict_put_str(&mut m, "id", &msg.id);
|
||||
dict_put_str(&mut m, "text", &msg.text.replace('\u{FFFC}', ""));
|
||||
dict_put_i64_as_str(&mut m, "date", msg.date.and_utc().timestamp());
|
||||
dict_put_str(&mut m, "sender", &msg.sender.display_name());
|
||||
items.push(Message::Dictionary(m));
|
||||
}
|
||||
let mut reply: XpcMap = HashMap::new();
|
||||
dict_put_str(&mut reply, "type", "GetMessagesResponse");
|
||||
reply.insert(cstr("messages"), Message::Array(items));
|
||||
Message::Dictionary(reply)
|
||||
}
|
||||
Err(e) => make_error_reply("DaemonError", &format!("{}", e)),
|
||||
}
|
||||
}
|
||||
|
||||
"DeleteAllConversations" => {
|
||||
match agent.send_event(Event::DeleteAllConversations).await {
|
||||
Ok(()) => make_ok_reply(),
|
||||
Err(e) => make_error_reply("DaemonError", &format!("{}", e)),
|
||||
}
|
||||
}
|
||||
|
||||
"SendMessage" => {
|
||||
let args = match get_dictionary_field(root, "arguments") { Some(a) => a, None => return make_error_reply("InvalidRequest", "Missing arguments") };
|
||||
let conversation_id = match dict_get_str(args, "conversation_id") { Some(v) => v, None => return make_error_reply("InvalidRequest", "Missing conversation_id") };
|
||||
let text = dict_get_str(args, "text").unwrap_or_default();
|
||||
let attachment_guids: Vec<String> = match args.get(&cstr("attachment_guids")) {
|
||||
Some(Message::Array(arr)) => arr.iter().filter_map(|m| match m { Message::String(s) => Some(s.to_string_lossy().into_owned()), _ => None }).collect(),
|
||||
_ => Vec::new(),
|
||||
};
|
||||
match agent.send_event(|r| Event::SendMessage(conversation_id, text, attachment_guids, r)).await {
|
||||
Ok(uuid) => {
|
||||
let mut reply: XpcMap = HashMap::new();
|
||||
dict_put_str(&mut reply, "type", "SendMessageResponse");
|
||||
dict_put_str(&mut reply, "uuid", &uuid.to_string());
|
||||
Message::Dictionary(reply)
|
||||
}
|
||||
Err(e) => make_error_reply("DaemonError", &format!("{}", e)),
|
||||
}
|
||||
}
|
||||
|
||||
"GetAttachmentInfo" => {
|
||||
let args = match get_dictionary_field(root, "arguments") { Some(a) => a, None => return make_error_reply("InvalidRequest", "Missing arguments") };
|
||||
let attachment_id = match dict_get_str(args, "attachment_id") { Some(v) => v, None => return make_error_reply("InvalidRequest", "Missing attachment_id") };
|
||||
match agent.send_event(|r| Event::GetAttachment(attachment_id, r)).await {
|
||||
Ok(attachment) => {
|
||||
let mut reply: XpcMap = HashMap::new();
|
||||
dict_put_str(&mut reply, "type", "GetAttachmentInfoResponse");
|
||||
dict_put_str(&mut reply, "path", &attachment.get_path_for_preview(false).to_string_lossy());
|
||||
dict_put_str(&mut reply, "preview_path", &attachment.get_path_for_preview(true).to_string_lossy());
|
||||
dict_put_str(&mut reply, "downloaded", &attachment.is_downloaded(false).to_string());
|
||||
dict_put_str(&mut reply, "preview_downloaded", &attachment.is_downloaded(true).to_string());
|
||||
Message::Dictionary(reply)
|
||||
}
|
||||
Err(e) => make_error_reply("DaemonError", &format!("{}", e)),
|
||||
}
|
||||
}
|
||||
|
||||
"DownloadAttachment" => {
|
||||
let args = match get_dictionary_field(root, "arguments") { Some(a) => a, None => return make_error_reply("InvalidRequest", "Missing arguments") };
|
||||
let attachment_id = match dict_get_str(args, "attachment_id") { Some(v) => v, None => return make_error_reply("InvalidRequest", "Missing attachment_id") };
|
||||
let preview = dict_get_str(args, "preview").map(|s| s == "true").unwrap_or(false);
|
||||
match agent.send_event(|r| Event::DownloadAttachment(attachment_id, preview, r)).await {
|
||||
Ok(()) => make_ok_reply(),
|
||||
Err(e) => make_error_reply("DaemonError", &format!("{}", e)),
|
||||
}
|
||||
}
|
||||
|
||||
"UploadAttachment" => {
|
||||
use std::path::PathBuf;
|
||||
let args = match get_dictionary_field(root, "arguments") { Some(a) => a, None => return make_error_reply("InvalidRequest", "Missing arguments") };
|
||||
let path = match dict_get_str(args, "path") { Some(v) => v, None => return make_error_reply("InvalidRequest", "Missing path") };
|
||||
match agent.send_event(|r| Event::UploadAttachment(PathBuf::from(path), r)).await {
|
||||
Ok(upload_guid) => {
|
||||
let mut reply: XpcMap = HashMap::new();
|
||||
dict_put_str(&mut reply, "type", "UploadAttachmentResponse");
|
||||
dict_put_str(&mut reply, "upload_guid", &upload_guid);
|
||||
Message::Dictionary(reply)
|
||||
}
|
||||
Err(e) => make_error_reply("DaemonError", &format!("{}", e)),
|
||||
}
|
||||
}
|
||||
|
||||
"GetAllSettings" => {
|
||||
match agent.send_event(Event::GetAllSettings).await {
|
||||
Ok(settings) => {
|
||||
let mut reply: XpcMap = HashMap::new();
|
||||
dict_put_str(&mut reply, "type", "GetAllSettingsResponse");
|
||||
dict_put_str(&mut reply, "server_url", &settings.server_url.unwrap_or_default());
|
||||
dict_put_str(&mut reply, "username", &settings.username.unwrap_or_default());
|
||||
Message::Dictionary(reply)
|
||||
}
|
||||
Err(e) => make_error_reply("DaemonError", &format!("{}", e)),
|
||||
}
|
||||
}
|
||||
|
||||
"UpdateSettings" => {
|
||||
let args = match get_dictionary_field(root, "arguments") { Some(a) => a, None => return make_error_reply("InvalidRequest", "Missing arguments") };
|
||||
let server_url = dict_get_str(args, "server_url");
|
||||
let username = dict_get_str(args, "username");
|
||||
let settings = Settings { server_url, username, token: None };
|
||||
match agent.send_event(|r| Event::UpdateSettings(settings, r)).await {
|
||||
Ok(()) => make_ok_reply(),
|
||||
Err(e) => make_error_reply("DaemonError", &format!("{}", e)),
|
||||
}
|
||||
}
|
||||
|
||||
// Unknown method fallback
|
||||
other => make_error_reply("UnknownMethod", other),
|
||||
}
|
||||
|
||||
@@ -273,41 +273,138 @@ impl DaemonInterface for XpcDaemonInterface {
|
||||
}
|
||||
}
|
||||
async fn sync_conversations(&mut self, _conversation_id: Option<String>) -> Result<()> {
|
||||
Err(anyhow::anyhow!("Feature not implemented for XPC"))
|
||||
let mach_port_name = Self::build_service_name()?;
|
||||
let mut client = XPCClient::connect(&mach_port_name);
|
||||
|
||||
if let Some(id) = _conversation_id {
|
||||
let mut args = HashMap::new();
|
||||
args.insert(Self::key("conversation_id"), Message::String(CString::new(id).unwrap()));
|
||||
let _ = self.call_method(&mut client, "SyncConversation", Some(args)).await?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let _ = self.call_method(&mut client, "SyncAllConversations", None).await?;
|
||||
Ok(())
|
||||
}
|
||||
async fn sync_conversations_list(&mut self) -> Result<()> {
|
||||
Err(anyhow::anyhow!("Feature not implemented for XPC"))
|
||||
let mach_port_name = Self::build_service_name()?;
|
||||
let mut client = XPCClient::connect(&mach_port_name);
|
||||
let _ = self.call_method(&mut client, "SyncConversationList", None).await?;
|
||||
Ok(())
|
||||
}
|
||||
async fn print_messages(
|
||||
&mut self,
|
||||
_conversation_id: String,
|
||||
_last_message_id: Option<String>,
|
||||
) -> Result<()> {
|
||||
Err(anyhow::anyhow!("Feature not implemented for XPC"))
|
||||
let mach_port_name = Self::build_service_name()?;
|
||||
let mut client = XPCClient::connect(&mach_port_name);
|
||||
|
||||
let mut args = HashMap::new();
|
||||
args.insert(Self::key("conversation_id"), Message::String(CString::new(_conversation_id).unwrap()));
|
||||
if let Some(last) = _last_message_id {
|
||||
args.insert(Self::key("last_message_id"), Message::String(CString::new(last).unwrap()));
|
||||
}
|
||||
|
||||
let reply = self.call_method(&mut client, "GetMessages", Some(args)).await?;
|
||||
match reply.get(&Self::key("messages")) {
|
||||
Some(Message::Array(items)) => {
|
||||
println!("Number of messages: {}", items.len());
|
||||
for item in items {
|
||||
if let Message::Dictionary(map) = item {
|
||||
let guid = Self::get_string(map, "id").map(|s| s.to_string_lossy().into_owned()).unwrap_or_default();
|
||||
let sender = Self::get_string(map, "sender").map(|s| s.to_string_lossy().into_owned()).unwrap_or_default();
|
||||
let text = Self::get_string(map, "text").map(|s| s.to_string_lossy().into_owned()).unwrap_or_default();
|
||||
let date_ts = Self::get_i64_from_str(map, "date").unwrap_or(0);
|
||||
let msg = crate::printers::PrintableMessage {
|
||||
guid,
|
||||
date: time::OffsetDateTime::from_unix_timestamp(date_ts).unwrap_or_else(|_| time::OffsetDateTime::UNIX_EPOCH),
|
||||
sender,
|
||||
text,
|
||||
file_transfer_guids: vec![],
|
||||
attachment_metadata: None,
|
||||
};
|
||||
println!("{}", crate::printers::MessagePrinter::new(&msg));
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
_ => Err(anyhow::anyhow!("Unexpected messages payload")),
|
||||
}
|
||||
}
|
||||
async fn enqueue_outgoing_message(
|
||||
&mut self,
|
||||
_conversation_id: String,
|
||||
_text: String,
|
||||
) -> Result<()> {
|
||||
Err(anyhow::anyhow!("Feature not implemented for XPC"))
|
||||
let mach_port_name = Self::build_service_name()?;
|
||||
let mut client = XPCClient::connect(&mach_port_name);
|
||||
let mut args = HashMap::new();
|
||||
args.insert(Self::key("conversation_id"), Message::String(CString::new(_conversation_id).unwrap()));
|
||||
args.insert(Self::key("text"), Message::String(CString::new(_text).unwrap()));
|
||||
let reply = self.call_method(&mut client, "SendMessage", Some(args)).await?;
|
||||
if let Some(uuid) = Self::get_string(&reply, "uuid") { println!("Outgoing message ID: {}", uuid.to_string_lossy()); }
|
||||
Ok(())
|
||||
}
|
||||
async fn wait_for_signals(&mut self) -> Result<()> {
|
||||
Err(anyhow::anyhow!("Feature not implemented for XPC"))
|
||||
}
|
||||
async fn config(&mut self, _cmd: ConfigCommands) -> Result<()> {
|
||||
Err(anyhow::anyhow!("Feature not implemented for XPC"))
|
||||
let mach_port_name = Self::build_service_name()?;
|
||||
let mut client = XPCClient::connect(&mach_port_name);
|
||||
match _cmd {
|
||||
ConfigCommands::Print => {
|
||||
let reply = self.call_method(&mut client, "GetAllSettings", None).await?;
|
||||
let server_url = Self::get_string(&reply, "server_url").map(|s| s.to_string_lossy().into_owned()).unwrap_or_default();
|
||||
let username = Self::get_string(&reply, "username").map(|s| s.to_string_lossy().into_owned()).unwrap_or_default();
|
||||
let table = prettytable::table!([b->"Server URL", &server_url], [b->"Username", &username]);
|
||||
table.printstd();
|
||||
Ok(())
|
||||
}
|
||||
ConfigCommands::SetServerUrl { url } => {
|
||||
let mut args = HashMap::new();
|
||||
args.insert(Self::key("server_url"), Message::String(CString::new(url).unwrap()));
|
||||
let _ = self.call_method(&mut client, "UpdateSettings", Some(args)).await?;
|
||||
Ok(())
|
||||
}
|
||||
ConfigCommands::SetUsername { username } => {
|
||||
let mut args = HashMap::new();
|
||||
args.insert(Self::key("username"), Message::String(CString::new(username).unwrap()));
|
||||
let _ = self.call_method(&mut client, "UpdateSettings", Some(args)).await?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
async fn delete_all_conversations(&mut self) -> Result<()> {
|
||||
Err(anyhow::anyhow!("Feature not implemented for XPC"))
|
||||
let mach_port_name = Self::build_service_name()?;
|
||||
let mut client = XPCClient::connect(&mach_port_name);
|
||||
let _ = self.call_method(&mut client, "DeleteAllConversations", None).await?;
|
||||
Ok(())
|
||||
}
|
||||
async fn download_attachment(&mut self, _attachment_id: String) -> Result<()> {
|
||||
Err(anyhow::anyhow!("Feature not implemented for XPC"))
|
||||
let mach_port_name = Self::build_service_name()?;
|
||||
let mut client = XPCClient::connect(&mach_port_name);
|
||||
let mut args = HashMap::new();
|
||||
args.insert(Self::key("attachment_id"), Message::String(CString::new(_attachment_id).unwrap()));
|
||||
args.insert(Self::key("preview"), Message::String(CString::new("false").unwrap()));
|
||||
let _ = self.call_method(&mut client, "DownloadAttachment", Some(args)).await?;
|
||||
Ok(())
|
||||
}
|
||||
async fn upload_attachment(&mut self, _path: String) -> Result<()> {
|
||||
Err(anyhow::anyhow!("Feature not implemented for XPC"))
|
||||
let mach_port_name = Self::build_service_name()?;
|
||||
let mut client = XPCClient::connect(&mach_port_name);
|
||||
let mut args = HashMap::new();
|
||||
args.insert(Self::key("path"), Message::String(CString::new(_path).unwrap()));
|
||||
let reply = self.call_method(&mut client, "UploadAttachment", Some(args)).await?;
|
||||
if let Some(guid) = Self::get_string(&reply, "upload_guid") { println!("Upload GUID: {}", guid.to_string_lossy()); }
|
||||
Ok(())
|
||||
}
|
||||
async fn mark_conversation_as_read(&mut self, _conversation_id: String) -> Result<()> {
|
||||
Err(anyhow::anyhow!("Feature not implemented for XPC"))
|
||||
let mach_port_name = Self::build_service_name()?;
|
||||
let mut client = XPCClient::connect(&mach_port_name);
|
||||
let mut args = HashMap::new();
|
||||
args.insert(Self::key("conversation_id"), Message::String(CString::new(_conversation_id).unwrap()));
|
||||
let _ = self.call_method(&mut client, "MarkConversationAsRead", Some(args)).await?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user