Skip to main content

gio/
file_enumerator.rs

1// Take a look at the license at the top of the repository in the LICENSE file.
2
3use crate::{FileEnumerator, FileInfo, prelude::*};
4use futures_core::future::LocalBoxFuture;
5use futures_util::FutureExt;
6use glib::translate::{ToGlibPtr, from_glib, from_glib_full};
7use std::{iter::FusedIterator, task::Poll};
8
9impl Iterator for FileEnumerator {
10    type Item = Result<FileInfo, glib::Error>;
11
12    fn next(&mut self) -> Option<Result<FileInfo, glib::Error>> {
13        match self.next_file(crate::Cancellable::NONE) {
14            // An already closed enumerator returns the same error forever,
15            // treat it as the end of the iterator instead.
16            Err(err) if err.kind::<crate::IOErrorEnum>() == Some(crate::IOErrorEnum::Closed) => {
17                None
18            }
19            Err(err) => Some(Err(err)),
20            Ok(file_info) => file_info.map(Ok),
21        }
22    }
23}
24
25impl FusedIterator for FileEnumerator {}
26
27pub trait FileEnumeratorExtManual: IsA<FileEnumerator> {
28    // rustdoc-stripper-ignore-next
29    /// Converts the enumerator into a [`Stream`](futures_core::Stream).
30    fn into_stream(self, num_files: i32, priority: glib::Priority) -> FileEnumeratorStream {
31        let future: Option<std::pin::Pin<Box<dyn Future<Output = _>>>> =
32            Some(Box::pin(self.next_files_future(num_files, priority)));
33        FileEnumeratorStream {
34            enumerator: self.upcast(),
35            future,
36            num_files,
37            priority,
38        }
39    }
40
41    /// Releases all resources used by this enumerator.
42    ///
43    /// The enumerator will return [error@Gio.IOErrorEnum.CLOSED] on all subsequent
44    /// calls.
45    ///
46    /// This will be automatically called when the last reference
47    /// is dropped, but you might want to call this function to make
48    /// sure resources are released as early as possible.
49    /// ## `cancellable`
50    /// optional cancellable object
51    ///
52    /// # Returns
53    ///
54    /// true on success; false otherwise
55    #[doc(alias = "g_file_enumerator_close")]
56    fn close(
57        &self,
58        cancellable: Option<&impl IsA<crate::Cancellable>>,
59    ) -> (bool, Option<glib::Error>) {
60        unsafe {
61            let mut error = std::ptr::null_mut();
62            let ret = crate::ffi::g_file_enumerator_close(
63                self.as_ref().to_glib_none().0,
64                cancellable.map(|p| p.as_ref()).to_glib_none().0,
65                &mut error,
66            );
67            (from_glib(ret), from_glib_full(error))
68        }
69    }
70}
71
72impl<O: IsA<FileEnumerator>> FileEnumeratorExtManual for O {}
73
74// rustdoc-stripper-ignore-next
75/// A [`Stream`](futures_core::Stream) used to enumerate files in directories.
76pub struct FileEnumeratorStream {
77    enumerator: FileEnumerator,
78    future: Option<LocalBoxFuture<'static, Result<Vec<FileInfo>, glib::Error>>>,
79    num_files: i32,
80    priority: glib::Priority,
81}
82
83impl std::fmt::Debug for FileEnumeratorStream {
84    #[inline]
85    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
86        f.debug_struct("FileEnumeratorStream")
87            .field("enumerator", &self.enumerator)
88            .field("num_files", &self.num_files)
89            .field("priority", &self.priority)
90            .finish()
91    }
92}
93
94impl futures_core::Stream for FileEnumeratorStream {
95    type Item = Result<Vec<FileInfo>, glib::Error>;
96
97    #[inline]
98    fn poll_next(
99        mut self: std::pin::Pin<&mut Self>,
100        cx: &mut std::task::Context<'_>,
101    ) -> Poll<Option<Self::Item>> {
102        match self.future.take() {
103            Some(mut f) => match f.poll_unpin(cx) {
104                Poll::Ready(Ok(fs)) if fs.is_empty() => Poll::Ready(None),
105                Poll::Ready(Ok(fs)) => {
106                    self.future = Some(Box::pin(
107                        self.enumerator
108                            .next_files_future(self.num_files, self.priority),
109                    ));
110                    Poll::Ready(Some(Ok(fs)))
111                }
112                Poll::Ready(Err(e)) => Poll::Ready(Some(Err(e))),
113                Poll::Pending => {
114                    self.future = Some(f);
115                    Poll::Pending
116                }
117            },
118            None => Poll::Ready(None),
119        }
120    }
121}
122
123impl futures_core::FusedStream for FileEnumeratorStream {
124    #[inline]
125    fn is_terminated(&self) -> bool {
126        self.future.is_none()
127    }
128}
129
130#[cfg(test)]
131mod tests {
132    use crate::prelude::*;
133    use futures_util::StreamExt;
134    use std::{cell::Cell, rc::Rc};
135    #[test]
136    fn file_enumerator_stream() {
137        let dir = std::env::current_dir().unwrap();
138        let ctx = glib::MainContext::new();
139        let lp = glib::MainLoop::new(Some(&ctx), false);
140        let res = Rc::new(Cell::new(None));
141
142        let lp_clone = lp.clone();
143        let res_clone = res.clone();
144        ctx.spawn_local(async move {
145            res_clone.replace(Some(
146                async {
147                    let dir = crate::File::for_path(dir);
148                    let mut stream = dir
149                        .enumerate_children_future(
150                            crate::FILE_ATTRIBUTE_STANDARD_NAME,
151                            crate::FileQueryInfoFlags::NONE,
152                            glib::Priority::default(),
153                        )
154                        .await?
155                        .into_stream(4, glib::Priority::default());
156                    while let Some(files) = stream.next().await {
157                        for file in files? {
158                            let _ = file.name();
159                        }
160                    }
161                    Ok::<_, glib::Error>(())
162                }
163                .await,
164            ));
165            lp_clone.quit();
166        });
167        lp.run();
168        // propagate any error from the future into a panic
169        Rc::try_unwrap(res)
170            .unwrap_or_else(|_| panic!("future not finished"))
171            .into_inner()
172            .unwrap()
173            .unwrap();
174    }
175
176    #[test]
177    fn file_enumerator_iterator_closed() {
178        let dir = crate::File::for_path(std::env::current_dir().unwrap());
179        let mut enumerator = dir
180            .enumerate_children(
181                crate::FILE_ATTRIBUTE_STANDARD_NAME,
182                crate::FileQueryInfoFlags::NONE,
183                crate::Cancellable::NONE,
184            )
185            .unwrap();
186
187        // Exhaust the iterator, which must end with `None`
188        while enumerator.next().is_some() {}
189
190        // A closed enumerator must also behave like an ended iterator
191        // instead of returning the same error forever
192        assert!(enumerator.close(crate::Cancellable::NONE).0);
193        assert!(enumerator.next().is_none());
194        assert!(enumerator.next().is_none());
195    }
196}