AirLibrary/Client/AirClient/mod.rs
1//! # Air::Client::AirClient
2//!
3//! gRPC client wrapper for the Air daemon service. Callers reach Air
4//! through this façade for update management, authentication, file
5//! indexing, and system monitoring.
6//!
7//! ## Layout
8//!
9//! Per-message DTOs live one-per-file under this module; each declares a
10//! single `pub struct Struct` to match the file name:
11//!
12//! - [`AirMetrics`] - daemon resource snapshot
13//! - [`AirStatus`] - daemon uptime / request totals
14//! - [`DownloadStreamChunk`] - one streaming download frame
15//! - [`ExtendedFileInfo`] - file metadata for index queries
16//! - [`FileInfo`] - downloaded-file metadata
17//! - [`FileResult`] - file-search hit
18//! - [`IndexInfo`] - indexing-progress snapshot
19//! - [`ResourceUsage`] - process resource counts
20//! - [`UpdateInfo`] - available-update metadata
21//! - [`DownloadStream`] - wraps `tonic::Streaming` to yield
22//! [`DownloadStreamChunk::Struct`] items via `.next().await`.
23//!
24//! ## Client core
25//!
26//! - [`AirClient`] struct - thread-safe gRPC client over `Arc<Mutex<…>>`
27//! - [`AirClient::new`] - async connect with parsed tonic `Endpoint`
28//! - [`AirClient::is_connected`] / [`AirClient::address`] accessors
29//! - [`Debug`] impl
30//! - [`IntoRequestExt`] - blanket helper to wrap any value as
31//! `tonic::Request<T>` with one method call
32//! - [`DEFAULT_AIR_SERVER_ADDRESS`] constant (`"[::1]:50053"`)
33
34// --- DTO submodules ---
35
36pub mod AirMetrics;
37
38pub mod AirStatus;
39
40pub mod DownloadStream;
41
42pub mod DownloadStreamChunk;
43
44pub mod ExtendedFileInfo;
45
46pub mod FileInfo;
47
48pub mod FileResult;
49
50pub mod IndexInfo;
51
52pub mod ResourceUsage;
53
54pub mod UpdateInfo;
55
56// --- Per-domain method impls (each declares its own `impl AirClient` block)
57// ---
58
59// Authentication
60pub mod Authenticate;
61
62// Updates
63pub mod ApplyUpdate;
64
65pub mod CheckForUpdates;
66
67pub mod DownloadUpdate;
68
69// Downloads
70pub mod DownloadFile;
71
72pub mod DownloadStreamRpc;
73
74// Indexing
75pub mod GetFileInfo;
76
77pub mod IndexFiles;
78
79pub mod SearchFiles;
80
81// Status + monitoring
82pub mod GetMetrics;
83
84pub mod GetResourceUsage;
85
86pub mod GetStatus;
87
88pub mod HealthCheck;
89
90pub mod SetResourceLimits;
91
92// Configuration
93pub mod GetConfiguration;
94
95pub mod UpdateConfiguration;
96
97// --- Client core ---
98
99use std::sync::Arc;
100
101use tokio::sync::Mutex;
102use tonic::transport::Channel;
103
104use crate::{AirError, Vine::Generated::air::air_service_client::AirServiceClient, dev_log};
105
106/// Default gRPC server address for the Air daemon.
107///
108/// Port allocation:
109///
110/// - `50051` - Mountain Vine server
111/// - `50052` - Cocoon Vine server
112/// - `50053` - Air Vine server (this constant)
113pub const DEFAULT_AIR_SERVER_ADDRESS:&str = "[::1]:50053";
114
115/// Air gRPC client wrapper.
116///
117/// Thread-safe via `Arc<Mutex<…>>`. Clones share the same underlying
118/// channel - clone is cheap (Arc ref-count bump). The `address` field is
119/// kept as the string the caller passed in so logs / `Debug` see the
120/// original form (`http://[::1]:50053`, etc.).
121#[derive(Clone)]
122pub struct AirClient {
123 /// Underlying tonic gRPC client wrapped in `Arc<Mutex<>>` for shared,
124 /// thread-safe access from multiple call sites.
125 client:Option<Arc<Mutex<AirServiceClient<Channel>>>>,
126
127 /// Address of the Air daemon.
128 address:String,
129}
130
131impl AirClient {
132 /// Connects to the Air daemon at `address` and returns a ready-to-use
133 /// client.
134 ///
135 /// # Arguments
136 ///
137 /// * `address` - gRPC server address (e.g. `"http://[::1]:50053"`).
138 ///
139 /// # Errors
140 ///
141 /// - [`AirError::Network`] if the address parses as a tonic `Endpoint` but
142 /// the underlying connection attempt fails.
143 /// - [`AirError::Validation`] if the address string is malformed.
144 pub async fn new(address:&str) -> Result<Self, AirError> {
145 dev_log!("grpc", "[AirClient] Connecting to Air daemon at: {}", address);
146
147 let Endpoint = address.parse::<tonic::transport::Endpoint>().map_err(|E| {
148 dev_log!("grpc", "error: [AirClient] Failed to parse address '{}': {}", address, E);
149
150 AirError::Validation(format!("Invalid address '{}': {}", address, E))
151 })?;
152
153 let Channel = Endpoint.connect().await.map_err(|E| {
154 dev_log!("grpc", "error: [AirClient] Failed to connect to Air daemon: {}", E);
155
156 AirError::Network(format!("Connection failed: {}", E))
157 })?;
158
159 dev_log!("grpc", "[AirClient] Successfully connected to Air daemon at: {}", address);
160
161 let Client = Arc::new(Mutex::new(AirServiceClient::new(Channel)));
162
163 Ok(Self { client:Some(Client), address:address.to_string() })
164 }
165
166 /// Whether the client is connected to the Air daemon.
167 pub fn is_connected(&self) -> bool { self.client.is_some() }
168
169 /// Address of the Air daemon.
170 pub fn address(&self) -> &str { &self.address }
171
172 /// Borrow the underlying tonic client for issuing RPCs. Returns
173 /// `None` when the client is disconnected. Per-domain method impls
174 /// call this and then `.lock().await` to obtain the mutex guard
175 /// before issuing the RPC.
176 pub(crate) fn Client(&self) -> Option<&Arc<Mutex<AirServiceClient<Channel>>>> { self.client.as_ref() }
177}
178
179impl std::fmt::Debug for AirClient {
180 fn fmt(&self, f:&mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "AirClient({})", self.address) }
181}
182
183// ============================================================================
184// tonic::Request Helper
185// ============================================================================
186
187/// Helper trait for converting any value into a `tonic::Request<T>`.
188///
189/// Implemented for every `T` via the blanket impl below. Per-domain method
190/// impls use `payload.into_request()` instead of
191/// `tonic::Request::new(payload)`.
192pub trait IntoRequestExt {
193 fn into_request(self) -> tonic::Request<Self>
194 where
195 Self: Sized, {
196 tonic::Request::new(self)
197 }
198}
199
200impl<T> IntoRequestExt for T {}