use crate::error::KanbusError;
use crate::notification_events::NotificationEvent;
use sha2::{Digest, Sha256};
#[cfg(unix)]
use std::io::Write;
#[cfg(unix)]
use std::os::unix::net::UnixStream;
use std::path::{Path, PathBuf};
fn get_socket_path(root: &Path) -> PathBuf {
let canonical = root.canonicalize().unwrap_or_else(|_| root.to_path_buf());
let mut hasher = Sha256::new();
hasher.update(canonical.to_string_lossy().as_bytes());
let hash = format!("{:x}", hasher.finalize());
let socket_name = format!("kanbus-{}.sock", &hash[..12]);
std::env::temp_dir().join(socket_name)
}
pub fn publish_notification(root: &Path, event: NotificationEvent) -> Result<(), KanbusError> {
let socket_path = get_socket_path(root);
use std::io::Write;
if let Ok(mut f) = std::fs::OpenOptions::new()
.create(true)
.append(true)
.open("/tmp/kanbus-cli-debug.log")
{
let _ = writeln!(
f,
"CLI: Sending notification to socket: {}",
socket_path.display()
);
}
let result = send_notification_sync(&socket_path, &event);
if let Err(e) = result {
if let Ok(mut f) = std::fs::OpenOptions::new()
.append(true)
.open("/tmp/kanbus-cli-debug.log")
{
let _ = writeln!(f, "CLI: Failed: {}", e);
}
eprintln!("Warning: Failed to send notification: {}", e);
} else if let Ok(mut f) = std::fs::OpenOptions::new()
.append(true)
.open("/tmp/kanbus-cli-debug.log")
{
let _ = writeln!(f, "CLI: Notification sent successfully");
}
Ok(())
}
#[cfg(unix)]
fn send_notification_sync(
socket_path: &Path,
event: &NotificationEvent,
) -> Result<(), KanbusError> {
let mut stream = UnixStream::connect(socket_path).map_err(|e| {
KanbusError::IssueOperation(format!(
"Console server not reachable (socket: {}): {}",
socket_path.display(),
e
))
})?;
let json_body = serde_json::to_string(event)
.map_err(|e| KanbusError::IssueOperation(format!("Failed to serialize event: {}", e)))?;
stream
.write_all(json_body.as_bytes())
.map_err(|e| KanbusError::IssueOperation(format!("Failed to write to socket: {}", e)))?;
stream
.write_all(b"\n")
.map_err(|e| KanbusError::IssueOperation(format!("Failed to write newline: {}", e)))?;
Ok(())
}
#[cfg(not(unix))]
fn send_notification_sync(
_socket_path: &Path,
_event: &NotificationEvent,
) -> Result<(), KanbusError> {
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use tempfile::tempdir;
#[test]
fn test_publish_notification_does_not_block() {
let temp_dir = tempdir().unwrap();
let event = NotificationEvent::IssueFocused {
issue_id: "test-123".to_string(),
user: None,
};
let result = publish_notification(temp_dir.path(), event);
assert!(result.is_ok());
}
#[test]
fn test_socket_path_is_deterministic() {
let temp_dir = tempdir().unwrap();
let path1 = get_socket_path(temp_dir.path());
let path2 = get_socket_path(temp_dir.path());
assert_eq!(path1, path2);
}
#[test]
fn test_socket_path_differs_by_project() {
let temp_dir1 = tempdir().unwrap();
let temp_dir2 = tempdir().unwrap();
let path1 = get_socket_path(temp_dir1.path());
let path2 = get_socket_path(temp_dir2.path());
assert_ne!(path1, path2);
}
}