Skip to main content

taskcluster_download/
factory.rs

1use anyhow::Result;
2use async_trait::async_trait;
3use std::io::{Cursor, SeekFrom};
4use tokio::fs::File;
5use tokio::io::{AsyncSeekExt, AsyncWrite, AsyncWriteExt};
6
7/// An AsyncWriterFactory can produce, on demand, an [AsyncWrite] object.  In the event of a
8/// download failure, the restarted download will use a fresh writer to restart writing at the
9/// beginning.
10#[async_trait]
11pub trait AsyncWriterFactory {
12    /// Get a fresh [AsyncWrite] object, positioned at the point where downloaded data should
13    /// be written.
14    ///
15    /// The `content_length` parameter holds the response's `Content-Length` if it's known (see
16    /// [content_length](reqwest::Response::content_length)). It's only a hint intended for sizing
17    /// the writer, and must not be relied upon. It might be `None` for chunked or decompressed
18    /// responses, and for a compressed body it reflects the compressed size, so the number of bytes
19    /// actually written may differ.
20    async fn get_writer<'a>(
21        &'a mut self,
22        content_length: Option<u64>,
23    ) -> Result<Box<dyn AsyncWrite + Unpin + 'a>>;
24}
25
26/// A CusorWriterFactory creates [AsyncWrite] objects from a [std::io::Cursor], allowing
27/// downloads to in-memory buffers.  It is specialized for [Vec<u8>] (which grows indefinitely)
28/// and `&mut [u8]` (which has a fixed maximum size)
29pub struct CursorWriterFactory<T>(Cursor<T>);
30
31#[async_trait]
32impl AsyncWriterFactory for CursorWriterFactory<Vec<u8>> {
33    async fn get_writer<'a>(
34        &'a mut self,
35        _: Option<u64>,
36    ) -> Result<Box<dyn AsyncWrite + Unpin + 'a>> {
37        self.0.get_mut().clear();
38        self.0.set_position(0);
39        Ok(Box::new(&mut self.0))
40    }
41}
42
43#[async_trait]
44impl AsyncWriterFactory for CursorWriterFactory<&mut [u8]> {
45    async fn get_writer<'a>(
46        &'a mut self,
47        _: Option<u64>,
48    ) -> Result<Box<dyn AsyncWrite + Unpin + 'a>> {
49        self.0.set_position(0);
50        Ok(Box::new(&mut self.0))
51    }
52}
53
54impl Default for CursorWriterFactory<Vec<u8>> {
55    fn default() -> Self {
56        Self(Cursor::new(Vec::new()))
57    }
58}
59
60impl CursorWriterFactory<Vec<u8>> {
61    pub fn new() -> Self {
62        Self::default()
63    }
64
65    /// Consume the factory, returning the vector into which the data was read
66    pub fn into_inner(self) -> Vec<u8> {
67        self.0.into_inner()
68    }
69}
70
71impl<'a> CursorWriterFactory<&'a mut [u8]> {
72    pub fn for_buf(inner: &'a mut [u8]) -> Self {
73        Self(Cursor::new(inner))
74    }
75
76    /// Return the size of the data written to the buffer.  This value should
77    /// be used to slice the resulting data from the buffer.
78    pub fn size(self) -> usize {
79        self.0.position() as usize
80    }
81}
82
83/// A FileWriterFactory creates [AsyncWrite] objects by rewinding and cloning a [tokio::fs::File].
84/// The file must be open in write mode and must be clone-able (that is, [File::try_clone()] must
85/// succeed) in order to support retried uploads.
86pub struct FileWriterFactory(File);
87
88#[async_trait]
89impl AsyncWriterFactory for FileWriterFactory {
90    async fn get_writer<'a>(
91        &'a mut self,
92        _: Option<u64>,
93    ) -> Result<Box<dyn AsyncWrite + Unpin + 'a>> {
94        let mut file = self.0.try_clone().await?;
95        file.set_len(0).await?;
96        file.seek(SeekFrom::Start(0)).await?;
97        Ok(Box::new(file))
98    }
99}
100
101impl FileWriterFactory {
102    pub fn new(file: File) -> Self {
103        Self(file)
104    }
105
106    /// Return the File, after finishing any concurrent async operations.  The
107    /// file posiion is unspecified.
108    pub async fn into_inner(mut self) -> Result<File> {
109        self.0.flush().await?;
110        Ok(self.0)
111    }
112}
113
114#[cfg(test)]
115mod test {
116    use super::*;
117    use anyhow::Result;
118    use tempfile::tempfile;
119    use tokio::io::{copy, AsyncReadExt, AsyncSeekExt};
120
121    const DATA: &[u8] = b"HELLO/WORLD";
122
123    async fn copy_to_factory<F: AsyncWriterFactory>(
124        data: &[u8],
125        factory: &mut F,
126    ) -> std::io::Result<()> {
127        let mut reader = Cursor::new(data);
128        let mut writer = factory.get_writer(Some(data.len() as u64)).await.unwrap();
129        copy(&mut reader, &mut writer).await?;
130        Ok(())
131    }
132
133    #[tokio::test]
134    async fn vec_cursor_writer_twice() -> Result<()> {
135        let mut factory = CursorWriterFactory::new();
136        copy_to_factory(b"wrong data, shouldn't see this", &mut factory).await?;
137        copy_to_factory(DATA, &mut factory).await?;
138        assert_eq!(&factory.into_inner(), DATA);
139        Ok(())
140    }
141
142    #[tokio::test]
143    async fn buf_cursor_writer_twice() -> Result<()> {
144        let mut buf = [0u8; 256];
145        let mut factory = CursorWriterFactory::for_buf(&mut buf[..]);
146        copy_to_factory(b"nobody should see this", &mut factory).await?;
147        copy_to_factory(DATA, &mut factory).await?;
148        let size = factory.size();
149        assert_eq!(&buf[..size], DATA);
150        Ok(())
151    }
152
153    #[tokio::test]
154    async fn buf_cursor_writer_too_small() -> Result<()> {
155        let mut buf = [0u8; 5];
156        let mut factory = CursorWriterFactory::for_buf(&mut buf[..]);
157        let err = copy_to_factory(DATA, &mut factory).await.unwrap_err();
158        assert_eq!(err.kind(), std::io::ErrorKind::WriteZero);
159        Ok(())
160    }
161
162    #[tokio::test]
163    async fn file_writer_twice() -> Result<()> {
164        let mut factory = FileWriterFactory::new(tempfile()?.into());
165        copy_to_factory(b"wrong data, shouldn't see this", &mut factory).await?;
166        copy_to_factory(DATA, &mut factory).await?;
167
168        let mut file = factory.into_inner().await?;
169        file.seek(SeekFrom::Start(0)).await?;
170
171        let mut res = Vec::new();
172        file.read_to_end(&mut res).await?;
173        assert_eq!(&res, DATA);
174        Ok(())
175    }
176}