pueue_lib/network_blocking/client.rs
1//! A reference implementation of a simple client that you may use.
2//! En/disable via the `client` feature.
3use color_eyre::{
4 Result,
5 eyre::{Context, bail},
6};
7use serde::Serialize;
8
9use super::protocol::*;
10use crate::{Error, PROTOCOL_VERSION, internal_prelude::*, message::*};
11
12/// This struct contains the base logic for the client.
13/// The client is responsible for connecting to the daemon, sending instructions
14/// and interpreting their responses.
15///
16/// ```no_run
17/// use std::path::PathBuf;
18/// use pueue_lib::{
19/// BlockingClient,
20/// Response,
21/// Request,
22/// network_blocking::socket::ConnectionSettings,
23/// };
24/// # use color_eyre::{Result, eyre::Context};
25///
26/// # #[cfg(target_os = "windows")]
27/// # fn main() {}
28///
29/// # #[cfg(not(target_os = "windows"))]
30/// # fn main() -> Result<()> {
31///
32/// // Connection settings and secret to connect to the daemon.
33/// let settings = ConnectionSettings::UnixSocket {
34/// path: PathBuf::from("/home/user/.local/share/pueue/pueue.socket"),
35/// };
36/// let secret = "My secret";
37///
38/// // Create a client. This already establishes a connection to the daemon.
39/// let mut client = BlockingClient::new(settings, secret.as_bytes(), true)
40/// .context("Failed to initialize client.")?;
41///
42/// // Request the state.
43/// client.send_request(Request::Status)?;
44/// let response: Response = client.receive_response()?;
45///
46/// let _state = match response {
47/// Response::Status(state) => state,
48/// _ => unreachable!(),
49/// };
50/// # Ok(())
51/// # }
52/// ```
53pub struct BlockingClient {
54 pub stream: GenericBlockingStream,
55 pub daemon_version: String,
56}
57
58impl std::fmt::Debug for BlockingClient {
59 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
60 f.debug_struct("Client")
61 .field("stream", &"GenericStream<not_debuggable>")
62 .finish()
63 }
64}
65
66impl BlockingClient {
67 /// Initialize a new client.
68 /// This includes establishing a connection to the daemon:
69 /// - Connect to the daemon.
70 /// - Authorize via secret.
71 /// - Check versions incompatibilities.
72 ///
73 /// If the `show_version_warning` flag is `true` and the daemon has a different version than
74 /// the client, a warning will be logged.
75 pub fn new(
76 settings: ConnectionSettings<'_>,
77 secret: &[u8],
78 show_version_warning: bool,
79 ) -> Result<Self> {
80 // Connect to daemon and get stream used for communication.
81 let mut stream = get_client_stream(settings).context("Failed to initialize stream.")?;
82
83 // Next we do a handshake with the daemon
84 // 1. Client sends the secret to the daemon.
85 // 2. If successful, the daemon responds with their version.
86 send_bytes(secret, &mut stream).context("Failed to send secret.")?;
87
88 // Receive and parse the response. We expect the daemon's version as UTF-8.
89 let version_bytes = receive_bytes(&mut stream)
90 .context("Failed to receive version during handshake with daemon.")?;
91 if version_bytes.is_empty() {
92 bail!("Daemon went away after sending secret. Did you use the correct secret?")
93 }
94 let daemon_version = match String::from_utf8(version_bytes) {
95 Ok(version) => version,
96 Err(_) => {
97 bail!("Daemon sent invalid UTF-8. Did you use the correct secret?")
98 }
99 };
100
101 // Info if the daemon runs a different protocol version.
102 // Backward compatibility should work, but some features might not work as expected.
103 if daemon_version != PROTOCOL_VERSION && show_version_warning {
104 warn!(
105 "Different protocol version detected '{daemon_version}'. Consider updating and restarting the daemon."
106 );
107 }
108
109 Ok(BlockingClient {
110 stream,
111 daemon_version,
112 })
113 }
114
115 /// Convenience function to get a mutable handle on the client's stream.
116 pub fn stream(&mut self) -> &mut GenericBlockingStream {
117 &mut self.stream
118 }
119
120 /// Convenience wrapper around [`super::send_request`] to directly send [`Request`]s.
121 pub fn send_request<T>(&mut self, message: T) -> Result<(), Error>
122 where
123 T: Into<Request>,
124 T: Serialize + std::fmt::Debug,
125 {
126 send_message::<_, Request>(message, &mut self.stream)
127 }
128
129 /// Convenience wrapper that wraps `receive_message` for [`Response`]s
130 pub fn receive_response(&mut self) -> Result<Response, Error> {
131 receive_message::<Response>(&mut self.stream)
132 }
133
134 pub fn daemon_version(&self) -> &String {
135 &self.daemon_version
136 }
137}