diff --git a/kordophoned/src/xpc/agent.rs b/kordophoned/src/xpc/agent.rs index 61948f5..e337c08 100644 --- a/kordophoned/src/xpc/agent.rs +++ b/kordophoned/src/xpc/agent.rs @@ -110,9 +110,39 @@ fn make_error_reply(code: &str, message: &str) -> Message { Message::Dictionary(reply) } +type XpcMap = HashMap; + +fn dict_get_str(map: &XpcMap, key: &str) -> Option { + let k = CString::new(key).ok()?; + match map.get(&k) { + Some(Message::String(v)) => Some(v.to_string_lossy().into_owned()), + _ => None, + } +} + +fn dict_get_i64_from_str(map: &XpcMap, key: &str) -> Option { + dict_get_str(map, key).and_then(|s| s.parse::().ok()) +} + +fn dict_put_str(map: &mut XpcMap, key: &str, value: impl AsRef) { + map.insert(cstr(key), Message::String(cstr(value.as_ref()))); +} + +fn dict_put_i64_as_str(map: &mut XpcMap, key: &str, value: i64) { + dict_put_str(map, key, value.to_string()); +} + +fn array_from_strs(values: impl IntoIterator) -> Message { + let arr = values + .into_iter() + .map(|s| Message::String(cstr(&s))) + .collect(); + Message::Array(arr) +} + async fn dispatch(agent: &XpcAgent, root: &HashMap) -> Message { // Standardized request: { method: String, arguments: Dictionary? } - let method = match get_string_field(root, "method").or_else(|| get_string_field(root, "type")) { + let method = match dict_get_str(root, "method").or_else(|| dict_get_str(root, "type")) { Some(m) => m, None => return make_error_reply("InvalidRequest", "Missing method/type"), }; @@ -124,9 +154,9 @@ async fn dispatch(agent: &XpcAgent, root: &HashMap) -> Message "GetVersion" => { match agent.send_event(Event::GetVersion).await { Ok(version) => { - let mut reply: HashMap = HashMap::new(); - reply.insert(cstr("type"), Message::String(cstr("GetVersionResponse"))); - reply.insert(cstr("version"), Message::String(cstr(&version))); + let mut reply: XpcMap = HashMap::new(); + dict_put_str(&mut reply, "type", "GetVersionResponse"); + dict_put_str(&mut reply, "version", &version); Message::Dictionary(reply) } Err(e) => make_error_reply("DaemonError", &format!("{}", e)), @@ -139,8 +169,8 @@ async fn dispatch(agent: &XpcAgent, root: &HashMap) -> Message let mut offset: i32 = 0; if let Some(args) = get_dictionary_field(root, "arguments") { - if let Some(Message::String(v)) = args.get(&cstr("limit")) { limit = v.to_string_lossy().parse().unwrap_or(100); } - if let Some(Message::String(v)) = args.get(&cstr("offset")) { offset = v.to_string_lossy().parse().unwrap_or(0); } + if let Some(v) = dict_get_i64_from_str(args, "limit") { limit = v as i32; } + if let Some(v) = dict_get_i64_from_str(args, "offset") { offset = v as i32; } } match agent.send_event(|r| Event::GetAllConversations(limit, offset, r)).await { @@ -148,28 +178,28 @@ async fn dispatch(agent: &XpcAgent, root: &HashMap) -> Message // Build array of conversation dictionaries let mut items: Vec = Vec::with_capacity(conversations.len()); for conv in conversations { - let mut m: HashMap = HashMap::new(); - m.insert(cstr("guid"), Message::String(cstr(&conv.guid))); - m.insert(cstr("display_name"), Message::String(cstr(&conv.display_name.unwrap_or_default()))); - m.insert(cstr("unread_count"), Message::String(cstr(&(conv.unread_count as i64).to_string()))); - m.insert(cstr("last_message_preview"), Message::String(cstr(&conv.last_message_preview.unwrap_or_default()))); + let mut m: XpcMap = HashMap::new(); + dict_put_str(&mut m, "guid", &conv.guid); + dict_put_str(&mut m, "display_name", &conv.display_name.unwrap_or_default()); + dict_put_i64_as_str(&mut m, "unread_count", conv.unread_count as i64); + dict_put_str(&mut m, "last_message_preview", &conv.last_message_preview.unwrap_or_default()); // participants -> array of strings - let participants: Vec = conv + let participant_names: Vec = conv .participants .into_iter() - .map(|p| Message::String(cstr(&p.display_name()))) + .map(|p| p.display_name()) .collect(); - m.insert(cstr("participants"), Message::Array(participants)); + m.insert(cstr("participants"), array_from_strs(participant_names)); // date as unix timestamp (i64) - m.insert(cstr("date"), Message::String(cstr(&conv.date.and_utc().timestamp().to_string()))); + dict_put_i64_as_str(&mut m, "date", conv.date.and_utc().timestamp()); items.push(Message::Dictionary(m)); } - let mut reply: HashMap = HashMap::new(); - reply.insert(cstr("type"), Message::String(cstr("GetConversationsResponse"))); + let mut reply: XpcMap = HashMap::new(); + dict_put_str(&mut reply, "type", "GetConversationsResponse"); reply.insert(cstr("conversations"), Message::Array(items)); Message::Dictionary(reply) } diff --git a/kpcli/src/daemon/xpc.rs b/kpcli/src/daemon/xpc.rs index efd85fb..3595651 100644 --- a/kpcli/src/daemon/xpc.rs +++ b/kpcli/src/daemon/xpc.rs @@ -138,8 +138,14 @@ impl XpcDaemonInterface { } } + fn key(k: &str) -> CString { CString::new(k).unwrap() } + fn get_string<'a>(map: &'a HashMap, key: &str) -> Option<&'a CStr> { - map.get(&CString::new(key).ok()?).and_then(|v| match v { Message::String(s) => Some(s.as_c_str()), _ => None }) + map.get(&Self::key(key)).and_then(|v| match v { Message::String(s) => Some(s.as_c_str()), _ => None }) + } + + fn get_i64_from_str(map: &HashMap, key: &str) -> Option { + Self::get_string(map, key).and_then(|s| s.to_string_lossy().parse().ok()) } } @@ -180,7 +186,7 @@ impl DaemonInterface for XpcDaemonInterface { .await?; // Expect an array under "conversations" - match reply.get(&CString::new("conversations").unwrap()) { + match reply.get(&Self::key("conversations")) { Some(Message::Array(items)) => { println!("Number of conversations: {}", items.len()); @@ -191,10 +197,10 @@ impl DaemonInterface for XpcDaemonInterface { let display_name = Self::get_string(map, "display_name").map(|s| s.to_string_lossy().into_owned()); let last_preview = Self::get_string(map, "last_message_preview").map(|s| s.to_string_lossy().into_owned()); - let unread_count = match map.get(&CString::new("unread_count").unwrap()) { Some(Message::String(v)) => v.to_string_lossy().parse().unwrap_or(0), _ => 0 }; - let date_ts: i64 = match map.get(&CString::new("date").unwrap()) { Some(Message::String(v)) => v.to_string_lossy().parse().unwrap_or(0), _ => 0 }; + let unread_count = Self::get_i64_from_str(map, "unread_count").unwrap_or(0) as i32; + let date_ts: i64 = Self::get_i64_from_str(map, "date").unwrap_or(0); - let participants: Vec = match map.get(&CString::new("participants").unwrap()) { + let participants: Vec = match map.get(&Self::key("participants")) { 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(), };