ssip_client_async/
fifo.rs

1// ssip-client -- Speech Dispatcher client in Rust
2// Copyright (c) 2021-2022 Laurent Pelecq
3//
4// Licensed under the Apache License, Version 2.0
5// <LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0> or the MIT
6// license <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
7// option. All files in the project carrying such notice may not be copied,
8// modified, or distributed except according to those terms.
9
10use std::env;
11use std::io;
12use std::path::{Path, PathBuf};
13
14const SPEECHD_APPLICATION_NAME: &str = "speech-dispatcher";
15const SPEECHD_SOCKET_NAME: &str = "speechd.sock";
16
17struct FifoPath {
18    path: Option<PathBuf>,
19}
20
21impl FifoPath {
22    fn new() -> FifoPath {
23        FifoPath { path: None }
24    }
25
26    fn set<P>(&mut self, path: P)
27    where
28        P: AsRef<Path>,
29    {
30        self.path = Some(path.as_ref().to_path_buf());
31    }
32
33    /// Return the standard socket according to the [freedesktop.org](https://www.freedesktop.org/) specification.
34    fn default_path() -> io::Result<PathBuf> {
35        match env::var_os("XDG_RUNTIME_DIR") {
36            Some(runtime_dir) => Ok(PathBuf::from(runtime_dir)
37                .join(SPEECHD_APPLICATION_NAME)
38                .join(SPEECHD_SOCKET_NAME)),
39            None => Err(io::Error::new(
40                io::ErrorKind::NotFound,
41                "unix socket not found",
42            )),
43        }
44    }
45
46    fn get(&self) -> io::Result<PathBuf> {
47        match &self.path {
48            Some(path) => Ok(path.to_path_buf()),
49            _ => FifoPath::default_path(),
50        }
51    }
52}
53
54pub mod synchronous {
55    use std::io::{self, BufReader, BufWriter};
56    pub use std::os::unix::net::UnixStream;
57    use std::path::Path;
58    use std::process::Command;
59    use std::time::Duration;
60
61    use crate::client::Client;
62    use crate::net::StreamMode;
63
64    use super::FifoPath;
65
66    pub struct Builder {
67        path: FifoPath,
68        mode: StreamMode,
69    }
70    impl Default for Builder {
71        fn default() -> Self {
72            Self::new()
73        }
74    }
75
76    impl Builder {
77        pub fn new() -> Self {
78            Self {
79                path: FifoPath::new(),
80                mode: StreamMode::Blocking,
81            }
82        }
83
84        pub fn path<P>(&mut self, socket_path: P) -> &mut Self
85        where
86            P: AsRef<Path>,
87        {
88            self.path.set(socket_path);
89            self
90        }
91
92        pub fn timeout(&mut self, read_timeout: Duration) -> &mut Self {
93            self.mode = StreamMode::TimeOut(read_timeout);
94            self
95        }
96
97        pub fn nonblocking(&mut self) -> &mut Self {
98            self.mode = StreamMode::NonBlocking;
99            self
100        }
101
102        /// Spawn the speech-dispatcher daemon before creating the client
103        pub fn with_spawn(&self) -> io::Result<&Self> {
104            Command::new("speech-dispatcher")
105                // respect the speechd `DisableAutoSpawn` setting
106                .args(["--spawn"])
107                .output()?;
108            Ok(self)
109        }
110
111        pub fn build(&self) -> io::Result<Client<UnixStream>> {
112            let input = UnixStream::connect(self.path.get()?)?;
113            match self.mode {
114                StreamMode::Blocking => input.set_nonblocking(false)?,
115                StreamMode::NonBlocking => input.set_nonblocking(true)?,
116                StreamMode::TimeOut(timeout) => input.set_read_timeout(Some(timeout))?,
117            }
118
119            let output = input.try_clone()?;
120            Ok(Client::new(BufReader::new(input), BufWriter::new(output)))
121        }
122    }
123}
124
125#[cfg(feature = "async-io")]
126pub mod asynchronous_async_io {
127    use crate::async_io::AsyncClient;
128    use async_net::unix::UnixStream;
129    use futures_lite::io::{self, BufReader};
130    use std::path::Path;
131
132    use super::FifoPath;
133
134    pub struct Builder {
135        path: FifoPath,
136    }
137    impl Default for Builder {
138        fn default() -> Self {
139            Self {
140                path: FifoPath::new(),
141            }
142        }
143    }
144
145    impl Builder {
146        pub fn path<P>(&mut self, socket_path: P) -> &mut Self
147        where
148            P: AsRef<Path>,
149        {
150            self.path.set(socket_path);
151            self
152        }
153
154        pub async fn build(&self) -> io::Result<AsyncClient<BufReader<UnixStream>, UnixStream>> {
155            let stream = UnixStream::connect(self.path.get()?).await?;
156            let (unbuf_read_stream, write_stream) = (stream.clone(), stream);
157            let read_stream = BufReader::new(unbuf_read_stream);
158            Ok(AsyncClient::new(read_stream, write_stream))
159        }
160    }
161}
162
163#[cfg(feature = "async-mio")]
164pub mod asynchronous_mio {
165    pub use mio::net::UnixStream;
166    use std::io::{self, BufReader, BufWriter};
167    use std::os::unix::net::UnixStream as StdUnixStream;
168    use std::path::Path;
169
170    use crate::client::MioClient as Client;
171
172    use super::FifoPath;
173
174    pub struct Builder {
175        path: FifoPath,
176    }
177    impl Default for Builder {
178        fn default() -> Self {
179            Self::new()
180        }
181    }
182
183    impl Builder {
184        pub fn new() -> Self {
185            Self {
186                path: FifoPath::new(),
187            }
188        }
189
190        fn non_blocking(socket: StdUnixStream) -> io::Result<StdUnixStream> {
191            socket.set_nonblocking(true)?;
192            Ok(socket)
193        }
194
195        pub fn path<P>(&mut self, socket_path: P) -> &mut Self
196        where
197            P: AsRef<Path>,
198        {
199            self.path.set(socket_path);
200            self
201        }
202
203        pub fn build(&self) -> io::Result<Client<UnixStream>> {
204            let stream = StdUnixStream::connect(self.path.get()?)?;
205            Ok(Client::new(
206                BufReader::new(UnixStream::from_std(Self::non_blocking(
207                    stream.try_clone()?,
208                )?)),
209                BufWriter::new(UnixStream::from_std(Self::non_blocking(stream)?)),
210            ))
211        }
212    }
213}
214
215#[cfg(feature = "tokio")]
216pub mod asynchronous_tokio {
217    use std::path::Path;
218    use tokio::io::{self, BufReader as AsyncBufReader, BufWriter as AsyncBufWriter};
219    pub use tokio::net::{unix::OwnedReadHalf, unix::OwnedWriteHalf, UnixStream};
220
221    use crate::tokio::AsyncClient;
222
223    use super::FifoPath;
224
225    pub struct Builder {
226        path: FifoPath,
227    }
228    impl Default for Builder {
229        fn default() -> Self {
230            Self {
231                path: FifoPath::new(),
232            }
233        }
234    }
235
236    impl Builder {
237        pub fn path<P>(&mut self, socket_path: P) -> &mut Self
238        where
239            P: AsRef<Path>,
240        {
241            self.path.set(socket_path);
242            self
243        }
244
245        pub async fn build(
246            &self,
247        ) -> io::Result<AsyncClient<AsyncBufReader<OwnedReadHalf>, AsyncBufWriter<OwnedWriteHalf>>>
248        {
249            let (read_stream, write_stream) =
250                UnixStream::connect(self.path.get()?).await?.into_split();
251            Ok(AsyncClient::new(
252                AsyncBufReader::new(read_stream),
253                AsyncBufWriter::new(write_stream),
254            ))
255        }
256    }
257}
258
259#[cfg(test)]
260mod tests {
261
262    #[test]
263    fn test_fifo_path() -> std::io::Result<()> {
264        if std::env::var("XDG_RUNTIME_DIR").is_ok() {
265            let socket_path = super::FifoPath::new();
266            assert!(socket_path
267                .get()?
268                .to_str()
269                .unwrap()
270                .ends_with("/speech-dispatcher/speechd.sock"));
271        }
272        Ok(())
273    }
274}