Skip to main content

mongodb/action/gridfs/
download.rs

1use crate::bson::{doc, Bson};
2
3use crate::{
4    action::{action_impl, deeplink, export_doc, option_setters, options_doc},
5    error::{ErrorKind, GridFsErrorKind, GridFsFileIdentifier, Result},
6    gridfs::{
7        FilesCollectionDocument,
8        GridFsBucket,
9        GridFsDownloadByNameOptions,
10        GridFsDownloadStream,
11    },
12};
13
14impl GridFsBucket {
15    /// Opens and returns a [`GridFsDownloadStream`] from which the application can read
16    /// the contents of the stored file specified by `id`.
17    ///
18    /// `await` will return d[`Result<GridFsDownloadStream>`].
19    #[deeplink]
20    pub fn open_download_stream(&self, id: Bson) -> OpenDownloadStream<'_> {
21        OpenDownloadStream { bucket: self, id }
22    }
23
24    /// Opens and returns a [`GridFsDownloadStream`] from which the application can read
25    /// the contents of the stored file specified by `filename`.
26    ///
27    /// If there are multiple files in the bucket with the given filename, the `revision` in the
28    /// options provided is used to determine which one to download. See the documentation for
29    /// [`GridFsDownloadByNameOptions`] for details on how to specify a revision. If no revision is
30    /// provided, the file with `filename` most recently uploaded will be downloaded.
31    ///
32    /// `await` will return d[`Result<GridFsDownloadStream>`].
33    #[deeplink]
34    #[options_doc(download_by_name)]
35    pub fn open_download_stream_by_name(
36        &self,
37        filename: impl Into<String>,
38    ) -> OpenDownloadStreamByName<'_> {
39        OpenDownloadStreamByName {
40            bucket: self,
41            filename: filename.into(),
42            options: None,
43        }
44    }
45
46    // Utility functions for finding files within the bucket.
47
48    async fn find_file_by_id(&self, id: &Bson) -> Result<FilesCollectionDocument> {
49        match self.find_one(doc! { "_id":  { "$eq": id } }).await? {
50            Some(file) => Ok(file),
51            None => Err(ErrorKind::GridFs(GridFsErrorKind::FileNotFound {
52                identifier: GridFsFileIdentifier::Id(id.clone()),
53            })
54            .into()),
55        }
56    }
57
58    async fn find_file_by_name(
59        &self,
60        filename: &str,
61        options: Option<GridFsDownloadByNameOptions>,
62    ) -> Result<FilesCollectionDocument> {
63        let revision = options.and_then(|opts| opts.revision).unwrap_or(-1);
64        let (sort, skip) = if revision >= 0 {
65            (1, revision)
66        } else {
67            (-1, -revision - 1)
68        };
69        // unwrap safety: `skip` is always >= 0
70        let skip: u64 = skip.try_into().unwrap();
71
72        match self
73            .files()
74            .find_one(doc! { "filename": filename })
75            .sort(doc! { "uploadDate": sort })
76            .skip(skip)
77            .await?
78        {
79            Some(fcd) => Ok(fcd),
80            None => {
81                if self
82                    .files()
83                    .find_one(doc! { "filename": filename })
84                    .await?
85                    .is_some()
86                {
87                    Err(ErrorKind::GridFs(GridFsErrorKind::RevisionNotFound { revision }).into())
88                } else {
89                    Err(ErrorKind::GridFs(GridFsErrorKind::FileNotFound {
90                        identifier: GridFsFileIdentifier::Filename(filename.into()),
91                    })
92                    .into())
93                }
94            }
95        }
96    }
97}
98
99#[cfg(feature = "sync")]
100impl crate::sync::gridfs::GridFsBucket {
101    /// Opens and returns a [`GridFsDownloadStream`] from which the application can read
102    /// the contents of the stored file specified by `id`.
103    ///
104    /// [`run`](OpenDownloadStream::run) will return d[`Result<GridFsDownloadStream>`].
105    #[deeplink]
106    pub fn open_download_stream(&self, id: Bson) -> OpenDownloadStream<'_> {
107        self.async_bucket.open_download_stream(id)
108    }
109
110    /// Opens and returns a [`GridFsDownloadStream`] from which the application can read
111    /// the contents of the stored file specified by `filename`.
112    ///
113    /// If there are multiple files in the bucket with the given filename, the `revision` in the
114    /// options provided is used to determine which one to download. See the documentation for
115    /// [`GridFsDownloadByNameOptions`] for details on how to specify a revision. If no revision is
116    /// provided, the file with `filename` most recently uploaded will be downloaded.
117    ///
118    /// [`run`](OpenDownloadStreamByName::run) will return d[`Result<GridFsDownloadStream>`].
119    #[deeplink]
120    #[options_doc(download_by_name, "run")]
121    pub fn open_download_stream_by_name(
122        &self,
123        filename: impl Into<String>,
124    ) -> OpenDownloadStreamByName<'_> {
125        self.async_bucket.open_download_stream_by_name(filename)
126    }
127}
128
129/// Opens and returns a [`GridFsDownloadStream`] from which the application can read
130/// the contents of the stored file specified by an id.  Construct with
131/// [`GridFsBucket::open_download_stream`].
132#[must_use]
133pub struct OpenDownloadStream<'a> {
134    bucket: &'a GridFsBucket,
135    id: Bson,
136}
137
138#[action_impl(sync = crate::sync::gridfs::GridFsDownloadStream)]
139impl<'a> Action for OpenDownloadStream<'a> {
140    type Future = OpenDownloadStreamFuture;
141
142    async fn execute(self) -> Result<GridFsDownloadStream> {
143        let file = self.bucket.find_file_by_id(&self.id).await?;
144        GridFsDownloadStream::new(file, self.bucket.chunks()).await
145    }
146}
147
148/// Opens and returns a [`GridFsDownloadStream`] from which the application can read
149/// the contents of the stored file specified by a filename.  Construct with
150/// [`GridFsBucket::open_download_stream_by_name`].
151#[must_use]
152pub struct OpenDownloadStreamByName<'a> {
153    bucket: &'a GridFsBucket,
154    filename: String,
155    options: Option<GridFsDownloadByNameOptions>,
156}
157
158#[option_setters(crate::gridfs::GridFsDownloadByNameOptions)]
159#[export_doc(download_by_name)]
160impl OpenDownloadStreamByName<'_> {}
161
162#[action_impl(sync = crate::sync::gridfs::GridFsDownloadStream)]
163impl<'a> Action for OpenDownloadStreamByName<'a> {
164    type Future = OpenDownloadStreamByNameFuture;
165
166    async fn execute(self) -> Result<GridFsDownloadStream> {
167        let file = self
168            .bucket
169            .find_file_by_name(&self.filename, self.options)
170            .await?;
171        GridFsDownloadStream::new(file, self.bucket.chunks()).await
172    }
173}