aboutsummaryrefslogtreecommitdiffstats
path: root/crates/daemon/src/api
diff options
context:
space:
mode:
authorBenedikt Peetz <benedikt.peetz@b-peetz.de>2026-07-20 19:30:40 +0200
committerBenedikt Peetz <benedikt.peetz@b-peetz.de>2026-07-20 19:30:40 +0200
commit966a80c4199a49898cc7d8641012d520ce6b2efa (patch)
tree51029ff75842090fd1eecbea97b6f7c447e3dea9 /crates/daemon/src/api
parentchore(server): Remove warnings (diff)
downloadatuin-966a80c4199a49898cc7d8641012d520ce6b2efa.zip
chore: Commit
Diffstat (limited to 'crates/daemon/src/api')
-rw-r--r--crates/daemon/src/api/client/mod.rs272
-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.rs28
-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.rs8
-rw-r--r--crates/daemon/src/api/server/mod.rs2
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;