cacache_sync/
put.rs

1//! Functions for writing to cache.
2use std::io::prelude::*;
3use std::path::{Path, PathBuf};
4
5use serde_json::Value;
6use ssri::{Algorithm, Integrity};
7
8use crate::content::write;
9use crate::errors::{Error, Internal, Result};
10use crate::index;
11
12/// Writes `data` to the `cache` synchronously, indexing it under `key`.
13///
14/// ## Example
15/// ```no_run
16/// use std::io::Read;
17///
18/// fn main() -> cacache_sync::Result<()> {
19///     let data = cacache_sync::write("./my-cache", "my-key", b"hello")?;
20///     Ok(())
21/// }
22/// ```
23pub fn write<P, D, K>(cache: P, key: K, data: D) -> Result<Integrity>
24where
25    P: AsRef<Path>,
26    D: AsRef<[u8]>,
27    K: AsRef<str>,
28{
29    let mut writer = Writer::create(cache.as_ref(), key.as_ref())?;
30    writer.write_all(data.as_ref()).with_context(|| {
31        format!(
32            "Failed to write to cache data for key {} for cache at {:?}",
33            key.as_ref(),
34            cache.as_ref()
35        )
36    })?;
37    writer.written = data.as_ref().len();
38    writer.commit()
39}
40
41/// Writes `data` to the `cache` synchronously, skipping associating a key with it.
42///
43/// ## Example
44/// ```no_run
45/// use std::io::Read;
46///
47/// fn main() -> cacache_sync::Result<()> {
48///     let data = cacache_sync::write_hash("./my-cache", b"hello")?;
49///     Ok(())
50/// }
51/// ```
52pub fn write_hash<P, D>(cache: P, data: D) -> Result<Integrity>
53where
54    P: AsRef<Path>,
55    D: AsRef<[u8]>,
56{
57    let mut writer = WriteOpts::new()
58        .algorithm(Algorithm::Sha256)
59        .size(data.as_ref().len())
60        .open_hash(cache.as_ref())?;
61    writer.write_all(data.as_ref()).with_context(|| {
62        format!(
63            "Failed to write to cache data for cache at {:?}",
64            cache.as_ref()
65        )
66    })?;
67    writer.written = data.as_ref().len();
68    writer.commit()
69}
70
71/// Builder for options and flags for opening a new cache file to write data into.
72#[derive(Clone, Default)]
73pub struct WriteOpts {
74    pub(crate) algorithm: Option<Algorithm>,
75    pub(crate) sri: Option<Integrity>,
76    pub(crate) size: Option<usize>,
77    pub(crate) time: Option<u128>,
78    pub(crate) metadata: Option<Value>,
79}
80
81impl WriteOpts {
82    /// Creates a blank set of cache writing options.
83    pub fn new() -> WriteOpts {
84        Default::default()
85    }
86
87    /// Opens the file handle for writing synchronously, returning a SyncWriter instance.
88    pub fn open<P, K>(self, cache: P, key: K) -> Result<Writer>
89    where
90        P: AsRef<Path>,
91        K: AsRef<str>,
92    {
93        Ok(Writer {
94            cache: cache.as_ref().to_path_buf(),
95            key: Some(String::from(key.as_ref())),
96            written: 0,
97            writer: write::Writer::new(
98                cache.as_ref(),
99                *self.algorithm.as_ref().unwrap_or(&Algorithm::Sha256),
100                self.size,
101            )?,
102            opts: self,
103        })
104    }
105
106    /// Opens the file handle for writing, without a key returning an SyncWriter instance.
107    pub fn open_hash<P>(self, cache: P) -> Result<Writer>
108    where
109        P: AsRef<Path>,
110    {
111        Ok(Writer {
112            cache: cache.as_ref().to_path_buf(),
113            key: None,
114            written: 0,
115            writer: write::Writer::new(
116                cache.as_ref(),
117                *self.algorithm.as_ref().unwrap_or(&Algorithm::Sha256),
118                self.size,
119            )?,
120            opts: self,
121        })
122    }
123
124    /// Configures the algorithm to write data under.
125    pub fn algorithm(mut self, algo: Algorithm) -> Self {
126        self.algorithm = Some(algo);
127        self
128    }
129
130    /// Sets the expected size of the data to write. If there's a date size
131    /// mismatch, `put.commit()` will return an error.
132    pub fn size(mut self, size: usize) -> Self {
133        self.size = Some(size);
134        self
135    }
136
137    /// Sets arbitrary additional metadata to associate with the index entry.
138    pub fn metadata(mut self, metadata: Value) -> Self {
139        self.metadata = Some(metadata);
140        self
141    }
142
143    /// Sets the specific time in unix milliseconds to associate with this
144    /// entry. This is usually automatically set to the write time, but can be
145    /// useful to change for tests and such.
146    pub fn time(mut self, time: u128) -> Self {
147        self.time = Some(time);
148        self
149    }
150
151    /// Sets the expected integrity hash of the written data. If there's a
152    /// mismatch between this Integrity and the one calculated by the write,
153    /// `put.commit()` will error.
154    pub fn integrity(mut self, sri: Integrity) -> Self {
155        self.sri = Some(sri);
156        self
157    }
158}
159
160/// A reference to an open file writing to the cache.
161pub struct Writer {
162    cache: PathBuf,
163    key: Option<String>,
164    written: usize,
165    pub(crate) writer: write::Writer,
166    opts: WriteOpts,
167}
168
169impl Write for Writer {
170    fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
171        let written = self.writer.write(buf)?;
172        self.written += written;
173        Ok(written)
174    }
175    fn flush(&mut self) -> std::io::Result<()> {
176        self.writer.flush()
177    }
178}
179
180impl Writer {
181    /// Creates a new writable file handle into the cache.
182    ///
183    /// ## Example
184    /// ```no_run
185    /// use std::io::prelude::*;
186    ///
187    /// fn main() -> cacache_sync::Result<()> {
188    ///     let mut fd = cacache_sync::Writer::create("./my-cache", "my-key")?;
189    ///     fd.write_all(b"hello world").expect("Failed to write to cache");
190    ///     // Data is not saved into the cache until you commit it.
191    ///     fd.commit()?;
192    ///     Ok(())
193    /// }
194    /// ```
195    pub fn create<P, K>(cache: P, key: K) -> Result<Writer>
196    where
197        P: AsRef<Path>,
198        K: AsRef<str>,
199    {
200        WriteOpts::new()
201            .algorithm(Algorithm::Sha256)
202            .open(cache.as_ref(), key.as_ref())
203    }
204
205    /// Closes the Writer handle and writes content and index entries. Also
206    /// verifies data against `size` and `integrity` options, if provided.
207    /// Must be called manually in order to complete the writing process,
208    /// otherwise everything will be thrown out.
209    pub fn commit(mut self) -> Result<Integrity> {
210        let cache = self.cache;
211        let writer_sri = self.writer.close()?;
212        if let Some(sri) = &self.opts.sri {
213            if sri.matches(&writer_sri).is_none() {
214                return Err(ssri::Error::IntegrityCheckError(sri.clone(), writer_sri).into());
215            }
216        } else {
217            self.opts.sri = Some(writer_sri.clone());
218        }
219        if let Some(size) = self.opts.size {
220            if size != self.written {
221                return Err(Error::SizeError(size, self.written));
222            }
223        }
224        if let Some(key) = self.key {
225            index::insert(&cache, &key, self.opts)
226        } else {
227            Ok(writer_sri)
228        }
229    }
230}
231
232#[cfg(test)]
233mod tests {
234
235    #[test]
236    fn round_trip() {
237        let tmp = tempfile::tempdir().unwrap();
238        let dir = tmp.path().to_owned();
239        crate::write(&dir, "hello", b"hello").unwrap();
240        let data = crate::read(&dir, "hello").unwrap();
241        assert_eq!(data, b"hello");
242    }
243
244    #[test]
245    fn hash_write() {
246        let tmp = tempfile::tempdir().unwrap();
247        let dir = tmp.path().to_owned();
248        let original = format!("hello world{}", 5);
249        let integrity = crate::write_hash(&dir, &original)
250            .expect("should be able to write a hash synchronously");
251        let bytes = crate::read_hash(&dir, &integrity)
252            .expect("should be able to read the data we just wrote");
253        let result =
254            String::from_utf8(bytes).expect("we wrote valid utf8 but did not read valid utf8 back");
255        assert_eq!(result, original, "we did not read back what we wrote");
256    }
257}