diff options
| author | Benedikt Peetz <benedikt.peetz@b-peetz.de> | 2026-07-20 19:30:40 +0200 |
|---|---|---|
| committer | Benedikt Peetz <benedikt.peetz@b-peetz.de> | 2026-07-20 19:30:40 +0200 |
| commit | 966a80c4199a49898cc7d8641012d520ce6b2efa (patch) | |
| tree | 51029ff75842090fd1eecbea97b6f7c447e3dea9 /crates/daemon/src/api | |
| parent | chore(server): Remove warnings (diff) | |
| download | atuin-966a80c4199a49898cc7d8641012d520ce6b2efa.zip | |
chore: Commit
Diffstat (limited to 'crates/daemon/src/api')
| -rw-r--r-- | crates/daemon/src/api/client/mod.rs | 272 | ||||
| -rw-r--r-- | crates/daemon/src/api/control.rs (renamed from crates/daemon/src/api/server/control.rs) | 16 | ||||
| -rw-r--r-- | crates/daemon/src/api/generated.rs | 28 | ||||
| -rw-r--r-- | crates/daemon/src/api/history.rs (renamed from crates/daemon/src/api/server/history.rs) | 13 | ||||
| -rw-r--r-- | crates/daemon/src/api/mod.rs | 8 | ||||
| -rw-r--r-- | crates/daemon/src/api/server/mod.rs | 2 |
6 files changed, 19 insertions, 320 deletions
diff --git a/crates/daemon/src/api/client/mod.rs b/crates/daemon/src/api/client/mod.rs deleted file mode 100644 index c588fb09..00000000 --- a/crates/daemon/src/api/client/mod.rs +++ /dev/null @@ -1,272 +0,0 @@ -use eyre::{Context as EyreContext, Result}; -use time::OffsetDateTime; -use tonic::Code; -use tonic::transport::{Channel, Endpoint, Uri}; -use tower::service_fn; - -use hyper_util::rt::TokioIo; - -#[cfg(unix)] -use tokio::net::UnixStream; - -use crate::api::generated; -use crate::api::generated::control::{ForceSyncReply, ForceSyncRequest, PathsReply, PathsRequest}; -use crate::api::generated::history::{HistoryEntry, HistoryRequest}; -use crate::{ - aclient::history::History, - api::{ - DAEMON_PROTOCOL_VERSION, DAEMON_VERSION, - generated::{ - control::{ - StatusReply, StatusRequest, control_client::ControlClient as ControlServiceClient, - }, - history::{ - EndHistoryReply, EndHistoryRequest, StartHistoryReply, StartHistoryRequest, - TailHistoryRequest, history_client::HistoryClient as HistoryServiceClient, - }, - }, - }, -}; - -pub use crate::api::generated::history::{HistoryEventKind, TailHistoryReply}; - -fn normalize_optional_field(value: &str) -> Option<String> { - let trimmed = value.trim(); - if trimmed.is_empty() { - None - } else { - Some(trimmed.to_owned()) - } -} - -pub fn history_entry_to_history(entry: HistoryEntry) -> History { - let timestamp = OffsetDateTime::from_unix_timestamp_nanos(i128::from(entry.timestamp)) - .expect("Daemon history timestamp should always be valid"); - - History { - id: entry.id.into(), - timestamp, - duration: entry.duration, - exit: entry.exit, - command: entry.command, - cwd: entry.cwd, - session: entry.session, - hostname: entry.hostname, - author: entry.author, - intent: normalize_optional_field(&entry.intent), - deleted_at: None, - } -} - -#[must_use] -pub fn daemon_matches_expected(version: &str, protocol: u32) -> bool { - version == DAEMON_VERSION && protocol == DAEMON_PROTOCOL_VERSION -} - -#[must_use] -pub fn daemon_mismatch_message(version: &str, protocol: u32) -> String { - if protocol == DAEMON_PROTOCOL_VERSION { - format!("daemon is out of date: expected {DAEMON_VERSION}, got {version}") - } else { - format!("daemon protocol mismatch: expected {DAEMON_PROTOCOL_VERSION}, got {protocol}") - } -} - -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub enum DaemonClientErrorKind { - Connect, - Unavailable, - Unimplemented, - Other, -} - -#[must_use] -pub fn classify_error(error: &eyre::Report) -> DaemonClientErrorKind { - for cause in error.chain() { - if cause.downcast_ref::<tonic::transport::Error>().is_some() { - return DaemonClientErrorKind::Connect; - } - - if let Some(status) = cause.downcast_ref::<tonic::Status>() { - return match status.code() { - Code::Unavailable => DaemonClientErrorKind::Unavailable, - Code::Unimplemented => DaemonClientErrorKind::Unimplemented, - _ => DaemonClientErrorKind::Other, - }; - } - } - - DaemonClientErrorKind::Other -} - -#[derive(Debug)] -pub enum Probe { - Ready(ControlClient), - NeedsRestart(String), - Unreachable(eyre::Report), -} - -/// Check if a client can reach the daemon. -pub async fn probe(path: String) -> Probe { - let mut client = match ControlClient::new(path).await { - Ok(client) => client, - Err(err) => return Probe::Unreachable(err), - }; - - match client.status().await { - Ok(status) => { - if daemon_matches_expected(&status.version, status.protocol) { - Probe::Ready(client) - } else { - Probe::NeedsRestart(daemon_mismatch_message(&status.version, status.protocol)) - } - } - Err(err) => Probe::Unreachable(err), - } -} - -// ============================================================================ -// History Client -// ============================================================================ - -#[derive(Debug)] -pub struct HistoryClient { - client: HistoryServiceClient<Channel>, -} - -pub struct Range { - pub start: OffsetDateTime, - pub end: OffsetDateTime, -} - -// Wrap the grpc client -impl HistoryClient { - #[cfg(unix)] - pub async fn new(path: String) -> Result<Self> { - use eyre::Context; - - let log_path = path.clone(); - let channel = Endpoint::try_from("http://atuin_local_daemon:0")? - .connect_with_connector(service_fn(move |_: Uri| { - let path = path.clone(); - - async move { - Ok::<_, std::io::Error>(TokioIo::new(UnixStream::connect(path.clone()).await?)) - } - })) - .await - .wrap_err_with(|| { - format!( - "failed to connect to local atuin daemon at {}. Is it running?", - &log_path - ) - })?; - - let client = HistoryServiceClient::new(channel); - - Ok(Self { client }) - } - - pub async fn start_history(&mut self, h: History) -> Result<StartHistoryReply> { - let req = StartHistoryRequest { - command: h.command, - cwd: h.cwd, - hostname: h.hostname, - session: h.session, - timestamp: h.timestamp.unix_timestamp_nanos() as u64, - author: h.author, - intent: h.intent.unwrap_or_default(), - }; - - Ok(self.client.start_history(req).await?.into_inner()) - } - - pub async fn history(&mut self, session: String, range: Option<Range>) -> Result<Vec<History>> { - let req = HistoryRequest { - session, - range: range.map(|r| generated::history::Range { - start: r.start.unix_timestamp() as u64, - end: r.end.unix_timestamp() as u64, - }), - }; - - let reply = self.client.history(req).await?.into_inner(); - - Ok(reply - .entries - .into_iter() - .map(history_entry_to_history) - .collect()) - } - - pub async fn end_history( - &mut self, - id: String, - duration: u64, - exit: i64, - ) -> Result<EndHistoryReply> { - let req = EndHistoryRequest { id, exit, duration }; - - Ok(self.client.end_history(req).await?.into_inner()) - } - - pub async fn tail_history(&mut self) -> Result<tonic::Streaming<TailHistoryReply>> { - Ok(self - .client - .tail_history(TailHistoryRequest {}) - .await? - .into_inner()) - } -} - -// ============================================================================ -// Control Client -// ============================================================================ - -/// Client for the Control gRPC service. -#[derive(Debug)] -pub struct ControlClient { - client: ControlServiceClient<Channel>, -} - -impl ControlClient { - /// Connect to the daemon's control service. - pub async fn new(path: String) -> Result<Self> { - let log_path = path.clone(); - let channel = Endpoint::try_from("http://atuin_local_daemon:0")? - .connect_with_connector(service_fn(move |_: Uri| { - let path = path.clone(); - - async move { - Ok::<_, std::io::Error>(TokioIo::new(UnixStream::connect(path.clone()).await?)) - } - })) - .await - .wrap_err_with(|| { - format!( - "failed to connect to local atuin daemon at {}. Is it running?", - &log_path - ) - })?; - - let client = ControlServiceClient::new(channel); - - Ok(Self { client }) - } - - pub async fn paths(&mut self) -> Result<PathsReply> { - Ok(self.client.paths(PathsRequest {}).await?.into_inner()) - } - - pub async fn force_sync(&mut self) -> Result<ForceSyncReply> { - Ok(self - .client - .force_sync(ForceSyncRequest {}) - .await? - .into_inner()) - } - - pub async fn status(&mut self) -> Result<StatusReply> { - Ok(self.client.status(StatusRequest {}).await?.into_inner()) - } -} diff --git a/crates/daemon/src/api/server/control.rs b/crates/daemon/src/api/control.rs index a5e26355..a9d9cff3 100644 --- a/crates/daemon/src/api/server/control.rs +++ b/crates/daemon/src/api/control.rs @@ -6,15 +6,17 @@ use tokio::time::{self, MissedTickBehavior}; use tonic::{Request, Response, Status}; use tracing::{Level, instrument}; +use turtle::generated::{ + DAEMON_PROTOCOL_VERSION, + control::{ + ForceSyncReply, ForceSyncRequest, PathsReply, PathsRequest, StatusReply, StatusRequest, + control_server::{Control, ControlServer}, + }, +}; + use crate::{ + DAEMON_VERSION, aclient::{history::store::HistoryStore, record::sync, settings::Settings}, - api::{ - DAEMON_PROTOCOL_VERSION, DAEMON_VERSION, - generated::control::{ - ForceSyncReply, ForceSyncRequest, PathsReply, PathsRequest, StatusReply, StatusRequest, - control_server::{Control, ControlServer}, - }, - }, daemon::DaemonHandle, events::DaemonEvent, }; diff --git a/crates/daemon/src/api/generated.rs b/crates/daemon/src/api/generated.rs deleted file mode 100644 index 304edcd9..00000000 --- a/crates/daemon/src/api/generated.rs +++ /dev/null @@ -1,28 +0,0 @@ -#![expect( - unreachable_pub, - unused_qualifications, - clippy::doc_markdown, - clippy::default_trait_access, - clippy::too_many_lines, - clippy::trivially_copy_pass_by_ref, - clippy::allow_attributes, - clippy::derive_partial_eq_without_eq, - reason = "All of these lints are triggered by the generated code" -)] - -/// Semantic command capture gRPC service types. -pub(crate) mod semantic { - tonic::include_proto!("semantic"); -} - -/// History module for the daemon gRPC history service. -/// -/// This module contains the proto-generated types for the history gRPC service. -pub(crate) mod history { - tonic::include_proto!("history"); -} - -/// Control module for external control. -pub(crate) mod control { - tonic::include_proto!("control"); -} diff --git a/crates/daemon/src/api/server/history.rs b/crates/daemon/src/api/history.rs index 0edf3b94..bcd2ee5a 100644 --- a/crates/daemon/src/api/server/history.rs +++ b/crates/daemon/src/api/history.rs @@ -10,20 +10,23 @@ use tracing::{Level, instrument}; use crate::{ aclient::{ database::{ClientSqlite, current_context}, - history::{History, HistoryId, store::HistoryStore}, + history::store::HistoryStore, settings::Settings, }, - api::{ + daemon::DaemonHandle, + events::DaemonEvent, +}; +use turtle::{ + generated::{ DAEMON_PROTOCOL_VERSION, - generated::history::{ + history::{ EndHistoryReply, EndHistoryRequest, HistoryEntry, HistoryEventKind, HistoryReply, HistoryRequest, StartHistoryReply, StartHistoryRequest, TailHistoryReply, TailHistoryRequest, history_server::{History as HistorySvc, HistoryServer}, }, }, - daemon::DaemonHandle, - events::DaemonEvent, + history::{History, HistoryId}, }; /// The gRPC service implementation. diff --git a/crates/daemon/src/api/mod.rs b/crates/daemon/src/api/mod.rs index b7f82a7b..8d475fe9 100644 --- a/crates/daemon/src/api/mod.rs +++ b/crates/daemon/src/api/mod.rs @@ -1,6 +1,2 @@ -pub mod client; -pub(crate) mod generated; -pub(crate) mod server; - -pub(crate) const DAEMON_VERSION: &str = env!("CARGO_PKG_VERSION"); -const DAEMON_PROTOCOL_VERSION: u32 = 1; +pub(crate) mod control; +pub(crate) mod history; diff --git a/crates/daemon/src/api/server/mod.rs b/crates/daemon/src/api/server/mod.rs deleted file mode 100644 index 8d475fe9..00000000 --- a/crates/daemon/src/api/server/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -pub(crate) mod control; -pub(crate) mod history; |
