diff --git a/client/Cargo.toml b/client/Cargo.toml index 8ab5625..e2c1ce5 100644 --- a/client/Cargo.toml +++ b/client/Cargo.toml @@ -15,4 +15,4 @@ tracing = { workspace = true } tracing-subscriber = { workspace = true } chrono = { workspace = true } xdg = { workspace = true } -rustyline = "12.0" +rustyline = "11.0" diff --git a/client/src/display.rs b/client/src/display.rs new file mode 100644 index 0000000..203e4a7 --- /dev/null +++ b/client/src/display.rs @@ -0,0 +1,56 @@ +//! Display utilities for the Cypraea client. +//! +//! This module contains functions for formatting and displaying information +//! from the Cypraea daemon. + +use std::io::{self, Write}; +use cypraea_common::protocol::SessionInfo; + +/// Format a session list for display. +pub fn format_session_list(sessions: &[SessionInfo]) -> String { + if sessions.is_empty() { + return "No active sessions.\n".to_string(); + } + + let mut result = String::new(); + result.push_str("Available sessions:\n"); + result.push_str("------------------\n"); + + for session in sessions { + result.push_str(&format!( + "- {} (created: {}, last used: {}, cmds: {}, {})\n", + session.id, + session.created_at.format("%Y-%m-%d %H:%M:%S"), + session.last_used.format("%Y-%m-%d %H:%M:%S"), + session.command_count, + if session.active { "active" } else { "idle" } + )); + } + + result +} + +/// Format session information for display. +pub fn format_session_info(session: &SessionInfo) -> String { + let mut result = String::new(); + result.push_str(&format!("Session: {}\n", session.id)); + result.push_str(&format!("Working directory: {}\n", session.cwd)); + result.push_str(&format!("Created: {}\n", session.created_at.format("%Y-%m-%d %H:%M:%S"))); + result.push_str(&format!("Last used: {}\n", session.last_used.format("%Y-%m-%d %H:%M:%S"))); + result.push_str(&format!("Commands executed: {}\n", session.command_count)); + result.push_str(&format!("Status: {}\n", if session.active { "active" } else { "idle" })); + + result +} + +/// Print a message to stdout. +pub fn print_message(msg: &str) -> io::Result<()> { + print!("{}", msg); + io::stdout().flush() +} + +/// Print a message to stderr. +pub fn print_error(msg: &str) -> io::Result<()> { + eprint!("{}", msg); + io::stderr().flush() +} diff --git a/client/src/main.rs b/client/src/main.rs index 1682d2b..d8eb2c8 100644 --- a/client/src/main.rs +++ b/client/src/main.rs @@ -79,18 +79,30 @@ async fn main() -> Result<()> { // Spawn a task to read messages from the daemon let read_task = tokio::spawn(async move { - let mut stream = Deserializer::from_reader(reader).into_iter::(); - - while let Some(msg_result) = stream.next() { - match msg_result { - Ok(msg) => { - debug!("Received message: {:?}", msg); - if tx.send(msg).await.is_err() { - break; + let mut buffer = String::new(); + + loop { + // Read a line from the stream + buffer.clear(); + match reader.read_line(&mut buffer).await { + Ok(0) => break, // End of stream + Ok(_) => { + // Parse the message + match serde_json::from_str::(&buffer) { + Ok(msg) => { + debug!("Received message: {:?}", msg); + if tx.send(msg).await.is_err() { + break; + } + } + Err(e) => { + error!("Error parsing message: {}", e); + // Continue trying to parse other messages + } } } Err(e) => { - error!("Error parsing message: {}", e); + error!("Error reading from daemon: {}", e); break; } } diff --git a/common/src/paths.rs b/common/src/paths.rs index 43396a1..982425a 100644 --- a/common/src/paths.rs +++ b/common/src/paths.rs @@ -8,7 +8,7 @@ use xdg::BaseDirectories; pub fn socket_path() -> Result { // Use XDG_RUNTIME_DIR if available, or fall back to a temporary directory let xdg = BaseDirectories::new().context("Failed to initialize XDG base directories")?; - Ok(xdg.get_runtime_path().join("cypraea.sock")) + Ok(xdg.place_runtime_file("cypraea.sock")?) } /// Get the path to the Cypraea database directory. diff --git a/daemon/Cargo.toml b/daemon/Cargo.toml index a582497..acad565 100644 --- a/daemon/Cargo.toml +++ b/daemon/Cargo.toml @@ -17,3 +17,4 @@ tracing = { workspace = true } tracing-subscriber = { workspace = true } chrono = { workspace = true } xdg = { workspace = true } +dirs-next = "2.0" diff --git a/daemon/src/db/mod.rs b/daemon/src/db/mod.rs index c0466d6..46d5762 100644 --- a/daemon/src/db/mod.rs +++ b/daemon/src/db/mod.rs @@ -75,7 +75,7 @@ impl Database { ) .context("Failed to create commands table")?; - Ok() + Ok(()) } /// Log a command execution. diff --git a/daemon/src/main.rs b/daemon/src/main.rs index 2aa0dba..37b0ca3 100644 --- a/daemon/src/main.rs +++ b/daemon/src/main.rs @@ -24,7 +24,7 @@ struct Args { database: Option, /// Enable debug logging - #[clap(short, long)] + #[clap(short = 'v', long)] debug: bool, } @@ -70,5 +70,5 @@ async fn main() -> Result<()> { .context("Socket server error")?; info!("Daemon shutting down"); - Ok() + Ok(()) } diff --git a/daemon/src/session/mod.rs b/daemon/src/session/mod.rs index c6c2dd2..016e97e 100644 --- a/daemon/src/session/mod.rs +++ b/daemon/src/session/mod.rs @@ -44,7 +44,7 @@ pub struct Session { impl Session { /// Create a new session. pub fn new(id: String) -> Self { - let home = dirs::home_dir().unwrap_or_else(|| PathBuf::from(".")); + let home = dirs_next::home_dir().unwrap_or_else(|| PathBuf::from(".")); let now = Utc::now(); Self { diff --git a/daemon/src/socket/mod.rs b/daemon/src/socket/mod.rs index 331bc5b..65c0b88 100644 --- a/daemon/src/socket/mod.rs +++ b/daemon/src/socket/mod.rs @@ -84,18 +84,38 @@ async fn handle_client(stream: UnixStream, session_manager: SessionManager) -> R }); // Process incoming messages - let mut stream = Deserializer::from_reader(reader).into_iter::(); - - while let Some(msg_result) = stream.next() { - let msg = msg_result.context("Failed to parse client message")?; - debug!("Received message: {:?}", msg); - - if let Err(e) = process_message(msg, &session_manager, tx.clone()).await { - error!("Error processing message: {}", e); - let error_msg = DaemonMessage::Error { - message: e.to_string(), - }; - tx.send(error_msg).await.ok(); + let mut buffer = String::new(); + loop { + // Read a line from the stream + buffer.clear(); + match reader.read_line(&mut buffer).await { + Ok(0) => break, // End of stream + Ok(_) => { + // Parse the message + match serde_json::from_str::(&buffer) { + Ok(msg) => { + debug!("Received message: {:?}", msg); + if let Err(e) = process_message(msg, &session_manager, tx.clone()).await { + error!("Error processing message: {}", e); + let error_msg = DaemonMessage::Error { + message: e.to_string(), + }; + tx.send(error_msg).await.ok(); + } + } + Err(e) => { + error!("Failed to parse client message: {}", e); + let error_msg = DaemonMessage::Error { + message: format!("Invalid message format: {}", e), + }; + tx.send(error_msg).await.ok(); + } + } + } + Err(e) => { + error!("Error reading from client: {}", e); + break; + } } }