Skip to main content

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