gio/subclass/
file_enumerator.rs1use glib::{prelude::*, subclass::prelude::*, translate::*};
4
5use crate::{ffi, prelude::*, Cancellable, FileEnumerator, FileInfo, IOErrorEnum};
6
7pub trait FileEnumeratorImpl: ObjectImpl + ObjectSubclass<Type: IsA<FileEnumerator>> {
10 fn next_file(
30 &self,
31 cancellable: Option<&Cancellable>,
32 ) -> Result<Option<FileInfo>, glib::Error> {
33 self.parent_next_file(cancellable)
34 }
35
36 fn close(&self, cancellable: Option<&Cancellable>) -> (bool, Option<glib::Error>) {
67 self.parent_close(cancellable)
68 }
69}
70
71pub trait FileEnumeratorImplExt: FileEnumeratorImpl {
74 fn parent_next_file(
75 &self,
76 cancellable: Option<&Cancellable>,
77 ) -> Result<Option<FileInfo>, glib::Error> {
78 if self.obj().is_closed() {
79 Err(glib::Error::new::<IOErrorEnum>(
80 IOErrorEnum::Closed,
81 "Enumerator is closed",
82 ))
83 } else {
84 unsafe {
85 let data = Self::type_data();
86 let parent_class = data.as_ref().parent_class() as *const ffi::GFileEnumeratorClass;
87
88 let f = (*parent_class)
89 .next_file
90 .expect("No parent class implementation for \"next_file\"");
91
92 let mut error = std::ptr::null_mut();
93 let res = f(
94 self.obj()
95 .unsafe_cast_ref::<FileEnumerator>()
96 .to_glib_none()
97 .0,
98 cancellable.as_ref().to_glib_none().0,
99 &mut error,
100 );
101 if error.is_null() {
102 Ok(from_glib_full(res))
103 } else {
104 Err(from_glib_full(error))
105 }
106 }
107 }
108 }
109
110 fn parent_close(&self, cancellable: Option<&Cancellable>) -> (bool, Option<glib::Error>) {
111 unsafe {
112 let data = Self::type_data();
113 let parent_class = data.as_ref().parent_class() as *const ffi::GFileEnumeratorClass;
114
115 let f = (*parent_class)
116 .close_fn
117 .expect("No parent class implementation for \"close_fn\"");
118
119 let mut error = std::ptr::null_mut();
120 let is_ok = f(
121 self.obj()
122 .unsafe_cast_ref::<FileEnumerator>()
123 .to_glib_none()
124 .0,
125 cancellable.as_ref().to_glib_none().0,
126 &mut error,
127 );
128 (from_glib(is_ok), from_glib_full(error))
129 }
130 }
131}
132
133impl<T: FileEnumeratorImpl> FileEnumeratorImplExt for T {}
134
135unsafe impl<T: FileEnumeratorImpl> IsSubclassable<T> for FileEnumerator {
137 fn class_init(class: &mut ::glib::Class<Self>) {
138 Self::parent_class_init::<T>(class);
139
140 let klass = class.as_mut();
141 klass.next_file = Some(next_file::<T>);
142 klass.close_fn = Some(close_fn::<T>);
143 }
150}
151
152unsafe extern "C" fn next_file<T: FileEnumeratorImpl>(
153 enumerator: *mut ffi::GFileEnumerator,
154 cancellable: *mut ffi::GCancellable,
155 error: *mut *mut glib::ffi::GError,
156) -> *mut ffi::GFileInfo {
157 let instance = &*(enumerator as *mut T::Instance);
158 let imp = instance.imp();
159 let cancellable = Option::<Cancellable>::from_glib_none(cancellable);
160
161 let res = imp.next_file(cancellable.as_ref());
162
163 match res {
164 Ok(fileinfo) => fileinfo.to_glib_full(),
165 Err(err) => {
166 if !error.is_null() {
167 *error = err.to_glib_full()
168 }
169 std::ptr::null_mut()
170 }
171 }
172}
173
174unsafe extern "C" fn close_fn<T: FileEnumeratorImpl>(
175 enumerator: *mut ffi::GFileEnumerator,
176 cancellable: *mut ffi::GCancellable,
177 error: *mut *mut glib::ffi::GError,
178) -> glib::ffi::gboolean {
179 let instance = &*(enumerator as *mut T::Instance);
180 let imp = instance.imp();
181 let cancellable = Option::<Cancellable>::from_glib_none(cancellable);
182
183 let res = imp.close(cancellable.as_ref());
184
185 if !error.is_null() {
186 *error = res.1.to_glib_full()
187 }
188
189 res.0.into_glib()
190}
191
192#[cfg(test)]
193mod tests {
194 use super::*;
198
199 mod imp {
201 use std::cell::Cell;
202
203 use super::*;
204
205 #[derive(Default)]
206 pub struct MyFileEnumerator(Cell<i32>);
207
208 #[glib::object_subclass]
209 impl ObjectSubclass for MyFileEnumerator {
210 const NAME: &'static str = "MyFileEnumerator";
211 type Type = super::MyFileEnumerator;
212 type ParentType = FileEnumerator;
213 }
214
215 impl ObjectImpl for MyFileEnumerator {
216 fn dispose(&self) {
217 let _ = self.obj().close(Cancellable::NONE);
218 }
219 }
220
221 impl FileEnumeratorImpl for MyFileEnumerator {
223 fn next_file(
224 &self,
225 cancellable: Option<&Cancellable>,
226 ) -> Result<Option<FileInfo>, glib::Error> {
227 if cancellable.is_some_and(|c| c.is_cancelled()) {
228 Err(glib::Error::new::<IOErrorEnum>(
229 IOErrorEnum::Cancelled,
230 "Operation was cancelled",
231 ))
232 } else {
233 match self.0.get() {
234 -1 => Err(glib::Error::new::<IOErrorEnum>(
235 IOErrorEnum::Closed,
236 "Enumerator is closed",
237 )),
238 i if i < 3 => {
239 let file_info = FileInfo::new();
240 file_info.set_display_name(&format!("file{i}"));
241 self.0.set(i + 1);
242 Ok(Some(file_info))
243 }
244 _ => Ok(None),
245 }
246 }
247 }
248
249 fn close(&self, cancellable: Option<&Cancellable>) -> (bool, Option<glib::Error>) {
250 if cancellable.is_some_and(|c| c.is_cancelled()) {
251 (
252 false,
253 Some(glib::Error::new::<IOErrorEnum>(
254 IOErrorEnum::Cancelled,
255 "Operation was cancelled",
256 )),
257 )
258 } else {
259 self.0.set(-1);
260 (true, None)
261 }
262 }
263 }
264
265 #[derive(Default)]
266 pub struct MyCustomFileEnumerator;
267
268 #[glib::object_subclass]
269 impl ObjectSubclass for MyCustomFileEnumerator {
270 const NAME: &'static str = "MyCustomFileEnumerator";
271 type Type = super::MyCustomFileEnumerator;
272 type ParentType = super::MyFileEnumerator;
273 }
274
275 impl ObjectImpl for MyCustomFileEnumerator {}
276
277 impl FileEnumeratorImpl for MyCustomFileEnumerator {}
279
280 impl MyFileEnumeratorImpl for MyCustomFileEnumerator {}
281 }
282
283 glib::wrapper! {
284 pub struct MyFileEnumerator(ObjectSubclass<imp::MyFileEnumerator>) @extends FileEnumerator;
285 }
286
287 pub trait MyFileEnumeratorImpl:
288 ObjectImpl + ObjectSubclass<Type: IsA<MyFileEnumerator> + IsA<FileEnumerator>>
289 {
290 }
291
292 unsafe impl<T: MyFileEnumeratorImpl + FileEnumeratorImpl> IsSubclassable<T> for MyFileEnumerator {}
294
295 glib::wrapper! {
296 pub struct MyCustomFileEnumerator(ObjectSubclass<imp::MyCustomFileEnumerator>) @extends MyFileEnumerator, FileEnumerator;
297 }
298
299 #[test]
300 fn file_enumerator_next_file() {
301 let my_custom_file_enumerator = glib::Object::new::<MyCustomFileEnumerator>();
303 let res = my_custom_file_enumerator.next_file(Cancellable::NONE);
304 assert!(res.as_ref().is_ok_and(|res| res.is_some()));
305 let filename = res.unwrap().unwrap().display_name();
306
307 let my_file_enumerator = glib::Object::new::<MyFileEnumerator>();
309 let res = my_file_enumerator.next_file(Cancellable::NONE);
310 assert!(res.as_ref().is_ok_and(|res| res.is_some()));
311 let expected = res.unwrap().unwrap().display_name();
312
313 assert_eq!(filename, expected);
315
316 for res in my_custom_file_enumerator.upcast::<FileEnumerator>() {
318 assert!(res.as_ref().is_ok());
319 let filename = res.unwrap().display_name();
320
321 let res = my_file_enumerator.next_file(Cancellable::NONE);
322 assert!(res.as_ref().is_ok_and(|res| res.is_some()));
323 let expected = res.unwrap().unwrap().display_name();
324
325 assert_eq!(filename, expected);
327 }
328 }
329
330 #[test]
331 fn file_enumerator_close() {
332 let my_custom_file_enumerator = glib::Object::new::<MyCustomFileEnumerator>();
334 let res = my_custom_file_enumerator.next_file(Cancellable::NONE);
335 assert!(res.as_ref().is_ok_and(|res| res.is_some()));
336 let filename = res.unwrap().unwrap().display_name();
337
338 let my_file_enumerator = glib::Object::new::<MyFileEnumerator>();
340 let res = my_file_enumerator.next_file(Cancellable::NONE);
341 assert!(res.as_ref().is_ok_and(|res| res.is_some()));
342 let expected = res.unwrap().unwrap().display_name();
343
344 assert_eq!(filename, expected);
346
347 let res = my_custom_file_enumerator.close(Cancellable::NONE);
349 assert_eq!(res.1, None);
350 let closed = res.0;
351
352 let res = my_file_enumerator.close(Cancellable::NONE);
354 assert_eq!(res.1, None);
355 let expected = res.0;
356
357 assert_eq!(closed, expected);
359
360 let res = my_custom_file_enumerator.next_file(Cancellable::NONE);
362 assert!(res.is_err());
363 let err = res.unwrap_err();
364
365 let res = my_file_enumerator.next_file(Cancellable::NONE);
367 assert!(res.is_err());
368 let expected = res.unwrap_err();
369
370 assert_eq!(err.domain(), expected.domain());
372 assert!(err.matches::<IOErrorEnum>(IOErrorEnum::Closed));
373 assert!(expected.matches::<IOErrorEnum>(IOErrorEnum::Closed));
374 assert_eq!(err.message(), expected.message());
375 }
376
377 #[test]
378 fn file_enumerator_cancel() {
379 let my_custom_file_enumerator = glib::Object::new::<MyCustomFileEnumerator>();
381 let res = my_custom_file_enumerator.next_file(Cancellable::NONE);
382 assert!(res.as_ref().is_ok_and(|res| res.is_some()));
383 let filename = res.unwrap().unwrap().display_name();
384
385 let my_file_enumerator = glib::Object::new::<MyFileEnumerator>();
387 let res = my_file_enumerator.next_file(Cancellable::NONE);
388 assert!(res.as_ref().is_ok_and(|res| res.is_some()));
389 let expected = res.unwrap().unwrap().display_name();
390
391 assert_eq!(filename, expected);
393
394 let cancellable = Cancellable::new();
396 cancellable.cancel();
397 let res = my_custom_file_enumerator.next_file(Some(&cancellable));
398 assert!(res.as_ref().is_err());
399 let err = res.unwrap_err();
400
401 let cancellable = Cancellable::new();
403 cancellable.cancel();
404 let res = my_file_enumerator.next_file(Some(&cancellable));
405 assert!(res.as_ref().is_err());
406 let expected = res.unwrap_err();
407
408 assert_eq!(err.domain(), expected.domain());
410 assert!(err.matches::<IOErrorEnum>(IOErrorEnum::Cancelled));
411 assert!(expected.matches::<IOErrorEnum>(IOErrorEnum::Cancelled));
412 assert_eq!(err.message(), expected.message());
413
414 let cancellable = Cancellable::new();
416 cancellable.cancel();
417 let res = my_custom_file_enumerator.close(Some(&cancellable));
418 assert!(res.1.is_some());
419 let err = res.1.unwrap();
420
421 let cancellable = Cancellable::new();
423 cancellable.cancel();
424 let res = my_file_enumerator.close(Some(&cancellable));
425 assert!(res.1.is_some());
426 let expected = res.1.unwrap();
427
428 assert_eq!(err.domain(), expected.domain());
430 assert!(err.matches::<IOErrorEnum>(IOErrorEnum::Cancelled));
431 assert!(expected.matches::<IOErrorEnum>(IOErrorEnum::Cancelled));
432 assert_eq!(err.message(), expected.message());
433 }
434}