Skip to main content

sequoia_ipc/
core.rs

1//! Contexts and errors.
2//!
3//! Sequoia tries to be useful for a wide variety of applications.
4//! Therefore, we need you to provide a little information about the
5//! context you are using Sequoia in.
6//!
7//! # Examples
8//!
9//! A context with reasonable defaults can be created using
10//! `Context::new`:
11//!
12//! ```no_run
13//! # use sequoia_ipc::{Context, Result};
14//! # fn main() -> Result<()> {
15//! let c = Context::new();
16//! # Ok(())
17//! # }
18//! ```
19
20#![warn(missing_docs)]
21
22use std::path::{Path, PathBuf};
23
24use crate::Result;
25
26/// A `Context` for Sequoia.
27///
28/// # Examples
29///
30/// A context with reasonable defaults can be created using
31/// `Context::new`:
32///
33/// ```no_run
34/// # use sequoia_ipc::{Context, Result};
35/// # fn main() -> Result<()> {
36/// let c = Context::new()?;
37/// # Ok(())
38/// # }
39/// ```
40///
41/// A context can be configured using the builder pattern with
42/// `Context::configure`:
43///
44/// ```
45/// # use sequoia_ipc::{Context, IPCPolicy, Result};
46/// # fn main() -> Result<()> {
47/// let c = Context::configure()
48/// #           .ephemeral()
49///             .ipc_policy(IPCPolicy::Robust)
50///             .build()?;
51/// # Ok(())
52/// # }
53/// ```
54pub struct Context {
55    home: PathBuf,
56    lib: PathBuf,
57    ipc_policy: IPCPolicy,
58    ephemeral: bool,
59    cleanup: bool,
60}
61
62impl Clone for Context {
63    fn clone(&self) -> Self {
64        Context {
65            home: self.home.clone(),
66            lib: self.lib.clone(),
67            ipc_policy: self.ipc_policy,
68            ephemeral: self.ephemeral,
69            cleanup: false, // Prevent cleanup.
70        }
71    }
72}
73
74impl Drop for Context {
75    fn drop(&mut self) {
76        use std::fs::remove_dir_all;
77
78        if self.ephemeral && self.cleanup {
79            let _ = remove_dir_all(&self.home);
80        }
81    }
82}
83
84/// Returns $PREXIX at compile-time, or a reasonable default prefix.
85fn prefix() -> PathBuf {
86    /* XXX: Windows support.  */
87    PathBuf::from(option_env!("PREFIX").unwrap_or("/usr/local"))
88}
89
90impl Context {
91    /// Creates a Context with reasonable defaults.
92    pub fn new() -> Result<Self> {
93        Self::configure().build()
94    }
95
96    /// Creates a Context that can be configured.
97    ///
98    /// The configuration is seeded like in `Context::new`, but can be
99    /// modified.  A configuration has to be finalized using
100    /// `.build()` in order to turn it into a Context.
101    pub fn configure() -> Config {
102        Config(Context {
103            home: PathBuf::from(""), // Defer computation of default.
104            lib: prefix().join("lib").join("sequoia"),
105            ipc_policy: IPCPolicy::Robust,
106            ephemeral: false,
107            cleanup: false,
108        })
109    }
110
111    /// Returns the directory containing shared state.
112    pub fn home(&self) -> &Path {
113        &self.home
114    }
115
116    /// Returns the directory containing backend servers.
117    pub fn lib(&self) -> &Path {
118        &self.lib
119    }
120
121    /// Returns the IPC policy.
122    pub fn ipc_policy(&self) -> &IPCPolicy {
123        &self.ipc_policy
124    }
125
126    /// Returns whether or not this is an ephemeral context.
127    pub fn ephemeral(&self) -> bool {
128        self.ephemeral
129    }
130}
131
132/// Represents a `Context` configuration.
133///
134/// A context can be configured using the builder pattern with
135/// `Context::configure`:
136///
137/// ```
138/// # use sequoia_ipc::{Context, IPCPolicy, Result};
139/// # fn main() -> Result<()> {
140/// let c = Context::configure()
141/// #           .ephemeral()
142///             .ipc_policy(IPCPolicy::Robust)
143///             .build()?;
144/// # Ok(())
145/// # }
146/// ```
147///
148/// You can create ephemeral context that are useful for tests and
149/// one-shot programs:
150///
151/// ```
152/// # use sequoia_ipc::{Context, Result};
153/// # use std::path::Path;
154/// # fn main() -> Result<()> {
155/// let c = Context::configure().ephemeral().build()?;
156/// let ephemeral_home = c.home().to_path_buf();
157/// // Do some tests.
158/// drop(c);
159/// assert!(! ephemeral_home.exists());
160/// # Ok(())
161/// # }
162/// ```
163pub struct Config(Context);
164
165impl Config {
166    /// Finalizes the configuration and returns a `Context`.
167    pub fn build(self) -> Result<Context> {
168        let mut c = self.0;
169
170        // As a special case, we defer the computation of the default
171        // home, because env::home_dir() may fail.
172        let home_not_set = c.home == PathBuf::from("");
173
174        // If we have an ephemeral home, and home is not explicitly
175        // set, create a temporary directory.  Ephemeral contexts can
176        // share home directories, e.g. client and server processes
177        // share one home.
178        if c.ephemeral && home_not_set {
179            let tmp = tempfile::Builder::new().prefix("sequoia").tempdir()?;
180            c.home = tmp.into_path();
181            c.cleanup = true;
182        } else if home_not_set {
183            c.home = dirs::home_dir()
184                .ok_or_else(|| anyhow::anyhow!("Failed to get users home directory"))?
185            .join(".sequoia");
186        }
187        Ok(c)
188    }
189
190    /// Sets the directory containing shared state.
191    pub fn home<P: AsRef<Path>>(mut self, home: P) -> Self {
192        self.set_home(home);
193        self
194    }
195
196    /// Sets the directory containing shared state and returns the old
197    /// home directory.
198    pub fn set_home<P: AsRef<Path>>(&mut self, home: P) -> PathBuf {
199        ::std::mem::replace(&mut self.0.home, PathBuf::new().join(home))
200    }
201
202    /// Sets the directory containing backend servers.
203    pub fn lib<P: AsRef<Path>>(mut self, lib: P) -> Self {
204        self.set_lib(lib);
205        self
206    }
207
208    /// Sets the directory containing backend servers and returns the
209    /// old library directory.
210    pub fn set_lib<P: AsRef<Path>>(&mut self, lib: P) -> PathBuf {
211        ::std::mem::replace(&mut self.0.lib, PathBuf::new().join(lib))
212    }
213
214    /// Sets the IPC policy.
215    pub fn ipc_policy(mut self, policy: IPCPolicy) -> Self {
216        self.set_ipc_policy(policy);
217        self
218    }
219
220    /// Sets the IPC policy and returns the old IPC policy.
221    pub fn set_ipc_policy(&mut self, policy: IPCPolicy) -> IPCPolicy {
222        ::std::mem::replace(&mut self.0.ipc_policy, policy)
223    }
224
225    /// Makes this context ephemeral.
226    pub fn ephemeral(mut self) -> Self {
227        self.set_ephemeral();
228        self
229    }
230
231    /// Makes this context ephemeral and returns the old ephemeral
232    /// state.
233    pub fn set_ephemeral(&mut self) -> bool {
234        ::std::mem::replace(&mut self.0.ephemeral, true)
235    }
236}
237
238/* IPC policy.  */
239
240/// IPC policy for Sequoia.
241///
242/// With this policy you can control how Sequoia starts background
243/// servers.
244#[derive(PartialEq, Debug, Copy, Clone)]
245pub enum IPCPolicy {
246    /// External background servers only.
247    ///
248    /// We will always use external background servers.  If starting
249    /// one fails, the operation will fail.
250    ///
251    /// The advantage is that we never spawn a thread.
252    ///
253    /// The disadvantage is that we need to locate the background
254    /// server to start.  If you are distribute Sequoia with your
255    /// application, make sure to include the binaries, and to
256    /// configure the Context so that `context.lib()` points to the
257    /// directory containing the binaries.
258    External,
259
260    /// Internal background servers only.
261    ///
262    /// We will always use internal background servers.  It is very
263    /// unlikely that this fails.
264    ///
265    /// The advantage is that this method is very robust.  If you
266    /// distribute Sequoia with your application, you do not need to
267    /// ship the binary, and it does not matter what `context.lib()`
268    /// points to.  This is very robust and convenient.
269    ///
270    /// The disadvantage is that we spawn a thread in your
271    /// application.  Threads may play badly with `fork(2)`, file
272    /// handles, and locks.  If you are not doing anything fancy,
273    /// however, and only use fork-then-exec, you should be okay.
274    Internal,
275
276    /// Prefer external, fall back to internal.
277    ///
278    /// We will first try to use an external background server, but
279    /// fall back on an internal one should that fail.
280    ///
281    /// The advantage is that if Sequoia is properly set up to find
282    /// the background servers, we will use these and get the
283    /// advantages of that approach.  Because we fail back on using an
284    /// internal server, we gain the robustness of that approach.
285    ///
286    /// The disadvantage is that we may or may not spawn a thread in
287    /// your application.  If this is unacceptable in your
288    /// environment, use the `External` policy.
289    Robust,
290}
291
292impl<'a> From<&'a IPCPolicy> for u8 {
293    fn from(policy: &IPCPolicy) -> Self {
294        match policy {
295            IPCPolicy::External => 0,
296            IPCPolicy::Internal => 1,
297            IPCPolicy::Robust => 2,
298        }
299    }
300}
301
302// XXX: TryFrom would be nice.
303impl From<u8> for IPCPolicy {
304    fn from(policy: u8) -> Self {
305        match policy {
306            0 => IPCPolicy::External,
307            1 => IPCPolicy::Internal,
308            2 => IPCPolicy::Robust,
309            n => panic!("Bad IPC policy: {}", n),
310        }
311    }
312}