Fix build and runtime issues

This commit includes:
- Fixed XDG path handling to use place_runtime_file instead of get_runtime_path
- Updated JSON deserialization for Tokio async compatibility in both client and daemon
- Added missing dirs-next dependency for home directory access
- Fixed return values in several functions
- Created display module for client UI formatting
- Fixed command-line argument conflicts in daemon
main
Developer 2025-04-06 03:36:02 +00:00
parent f286cc3271
commit bf4f757ef2
9 changed files with 116 additions and 27 deletions

View File

@ -15,4 +15,4 @@ tracing = { workspace = true }
tracing-subscriber = { workspace = true }
chrono = { workspace = true }
xdg = { workspace = true }
rustyline = "12.0"
rustyline = "11.0"

56
client/src/display.rs Normal file
View File

@ -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()
}

View File

@ -79,10 +79,16 @@ 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::<DaemonMessage>();
let mut buffer = String::new();
while let Some(msg_result) = stream.next() {
match msg_result {
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::<DaemonMessage>(&buffer) {
Ok(msg) => {
debug!("Received message: {:?}", msg);
if tx.send(msg).await.is_err() {
@ -91,6 +97,12 @@ async fn main() -> Result<()> {
}
Err(e) => {
error!("Error parsing message: {}", e);
// Continue trying to parse other messages
}
}
}
Err(e) => {
error!("Error reading from daemon: {}", e);
break;
}
}

View File

@ -8,7 +8,7 @@ use xdg::BaseDirectories;
pub fn socket_path() -> Result<PathBuf> {
// 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.

View File

@ -17,3 +17,4 @@ tracing = { workspace = true }
tracing-subscriber = { workspace = true }
chrono = { workspace = true }
xdg = { workspace = true }
dirs-next = "2.0"

View File

@ -75,7 +75,7 @@ impl Database {
)
.context("Failed to create commands table")?;
Ok()
Ok(())
}
/// Log a command execution.

View File

@ -24,7 +24,7 @@ struct Args {
database: Option<String>,
/// 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(())
}

View File

@ -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 {

View File

@ -84,12 +84,17 @@ async fn handle_client(stream: UnixStream, session_manager: SessionManager) -> R
});
// Process incoming messages
let mut stream = Deserializer::from_reader(reader).into_iter::<ClientMessage>();
while let Some(msg_result) = stream.next() {
let msg = msg_result.context("Failed to parse client message")?;
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::<ClientMessage>(&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 {
@ -98,6 +103,21 @@ async fn handle_client(stream: UnixStream, session_manager: SessionManager) -> R
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;
}
}
}
// Cancel the write task
write_task.abort();