From 9cdc575666faad4c162f4b4cf519bfdd1fd528dc Mon Sep 17 00:00:00 2001 From: Benedikt Peetz Date: Fri, 24 Jul 2026 17:56:36 +0200 Subject: feat: Finalize design for fish-shell integration --- crates/daemon/src/api/control.rs | 33 +++++++++++++++-- crates/daemon/src/api/history.rs | 80 +++++++++++++++++++++++++++------------- 2 files changed, 85 insertions(+), 28 deletions(-) (limited to 'crates/daemon/src/api') diff --git a/crates/daemon/src/api/control.rs b/crates/daemon/src/api/control.rs index ff19f593..8277f434 100644 --- a/crates/daemon/src/api/control.rs +++ b/crates/daemon/src/api/control.rs @@ -6,7 +6,7 @@ use tokio::time::{self, MissedTickBehavior}; use tonic::{Request, Response, Status}; use tracing::{Level, instrument}; -use turtle::generated::{ +use turtle_api::generated::{ DAEMON_PROTOCOL_VERSION, control::{ ForceSyncReply, ForceSyncRequest, PathsReply, PathsRequest, StatusReply, StatusRequest, @@ -91,9 +91,36 @@ impl Control for ControlService { &self, _request: Request, ) -> Result, Status> { - let reply = ForceSyncReply { accepted: true }; - self.handle.emit(DaemonEvent::ForceSync); + let event = self + .handle + .wait_for(|e| { + matches!( + e, + DaemonEvent::SyncFailed { .. } | DaemonEvent::SyncCompleted { .. } + ) + }) + .await + .map_err(|e| { + Status::internal(format!("failed to wait for sync response event: {e:?}")) + })?; + + let reply = match event { + DaemonEvent::SyncCompleted { + uploaded, + downloaded, + } => ForceSyncReply { + error: None, + uploaded: uploaded as u32, + downloaded: downloaded as u32, + }, + DaemonEvent::SyncFailed { error } => ForceSyncReply { + error: Some(error), + uploaded: 0, + downloaded: 0, + }, + _ => unreachable!(), + }; Ok(Response::new(reply)) } diff --git a/crates/daemon/src/api/history.rs b/crates/daemon/src/api/history.rs index 6165464d..0b373604 100644 --- a/crates/daemon/src/api/history.rs +++ b/crates/daemon/src/api/history.rs @@ -1,10 +1,10 @@ -use std::pin::Pin; +use std::{pin::Pin, time::Duration}; use dashmap::DashMap; use eyre::Result; use time::OffsetDateTime; use tokio_stream::Stream; -use tonic::{Request, Response, Status}; +use tonic::{IntoRequest, Request, Response, Status}; use tracing::{Level, instrument}; use crate::{ @@ -12,12 +12,16 @@ use crate::{ daemon::DaemonHandle, events::DaemonEvent, }; -use turtle::{ +use turtle_api::{ + client::{ + proto_duration_to_std, proto_timestamp_to_time, std_to_proto_duration, + time_to_proto_timestamp, + }, generated::{ DAEMON_PROTOCOL_VERSION, history::{ - EndHistoryReply, EndHistoryRequest, HistoryEntry, HistoryEventKind, HistoryReply, - HistoryRequest, StartHistoryReply, StartHistoryRequest, TailHistoryReply, + AddHistoryRequest, EndHistoryReply, EndHistoryRequest, HistoryEntry, HistoryEventKind, + HistoryReply, HistoryRequest, StartHistoryReply, StartHistoryRequest, TailHistoryReply, TailHistoryRequest, history_server::{History as HistorySvc, HistoryServer}, }, @@ -60,8 +64,8 @@ impl HistoryService { fn history_to_reply(history: History) -> HistoryEntry { HistoryEntry { - timestamp: history.timestamp.unix_timestamp_nanos() as u64, - id: history.id.0, + timestamp: time_to_proto_timestamp(history.timestamp), + id: history.id.to_string(), command: history.command, cwd: history.cwd, session: history.session, @@ -69,7 +73,7 @@ fn history_to_reply(history: History) -> HistoryEntry { author: history.author, intent: history.intent.unwrap_or_default(), exit: history.exit, - duration: history.duration, + duration: std_to_proto_duration(history.duration), } } @@ -85,8 +89,8 @@ impl HistorySvc for HistoryService { let req = request.into_inner(); let entries = if let Some(range) = req.range { - let from = OffsetDateTime::from_unix_timestamp(range.start as i64).unwrap(); - let to = OffsetDateTime::from_unix_timestamp(range.end as i64).unwrap(); + let from = proto_timestamp_to_time(range.start); + let to = proto_timestamp_to_time(range.end); self.handle.history_db().range(from, to).await } else { @@ -100,19 +104,42 @@ impl HistorySvc for HistoryService { Ok(Response::new(HistoryReply { entries })) } + #[instrument(skip_all, level = Level::INFO)] + async fn add_history( + &self, + request: Request, + ) -> Result, Status> { + let req = request.into_inner(); + let start_req = req.start.expect("is some"); + + let start_response = self + .start_history(start_req.into_request()) + .await? + .into_inner(); + let end_responnse = self + .end_history( + EndHistoryRequest { + id: start_response.id, + exit: req.exit, + duration: req.duration, + } + .into_request(), + ) + .await?; + + Ok(end_responnse) + } + #[instrument(skip_all, level = Level::INFO)] async fn start_history( &self, request: Request, ) -> Result, Status> { + tokio::time::sleep(Duration::from_secs(5)).await; + let req = request.into_inner(); - let timestamp = OffsetDateTime::from_unix_timestamp_nanos(i128::from(req.timestamp)) - .map_err(|_| { - Status::invalid_argument( - "failed to parse timestamp as unix time (expected nanos since epoch)", - ) - })?; + let timestamp = proto_timestamp_to_time(req.timestamp); let h: History = History::daemon() .timestamp(timestamp) @@ -121,7 +148,7 @@ impl HistorySvc for HistoryService { .session(req.session) .hostname(req.hostname) .author(req.author) - .intent(req.intent) + .intent(req.intent.unwrap_or_default()) .build() .into(); @@ -146,16 +173,15 @@ impl HistorySvc for HistoryService { request: Request, ) -> Result, Status> { let req = request.into_inner(); - let id = HistoryId(req.id); + let id = HistoryId::from(req.id); if let Some((_, mut history)) = self.running.remove(&id) { history.exit = req.exit; - history.duration = match req.duration { - 0 => i64::try_from( - (OffsetDateTime::now_utc() - history.timestamp).whole_nanoseconds(), - ) - .expect("failed to convert calculated duration to i64"), - value => i64::try_from(value).expect("failed to get i64 duration"), + history.duration = match proto_duration_to_std(req.duration) { + Duration::ZERO => Duration::from_nanos_u128( + (OffsetDateTime::now_utc() - history.timestamp).whole_nanoseconds() as u128, + ), + value => value, }; self.handle @@ -164,7 +190,11 @@ impl HistorySvc for HistoryService { .await .map_err(|e| Status::internal(format!("failed to write to db: {e:?}")))?; - tracing::info!(id = id.0, duration = history.duration, "end history"); + tracing::info!( + id = id.to_string(), + duration = history.duration.as_nanos(), + "end history" + ); let (record_id, idx) = self .history_store -- cgit v1.3.1