gio/subclass/
file_enumerator.rs1use glib::{prelude::*, subclass::prelude::*, translate::*};
4
5use crate::{Cancellable, FileEnumerator, FileInfo, IOErrorEnum, ffi, prelude::*};
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 unsafe {
158 let instance = &*(enumerator as *mut T::Instance);
159 let imp = instance.imp();
160 let cancellable = Option::<Cancellable>::from_glib_none(cancellable);
161
162 let res = imp.next_file(cancellable.as_ref());
163
164 match res {
165 Ok(fileinfo) => fileinfo.to_glib_full(),
166 Err(err) => {
167 if !error.is_null() {
168 *error = err.to_glib_full()
169 }
170 std::ptr::null_mut()
171 }
172 }
173 }
174}
175
176unsafe extern "C" fn close_fn<T: FileEnumeratorImpl>(
177 enumerator: *mut ffi::GFileEnumerator,
178 cancellable: *mut ffi::GCancellable,
179 error: *mut *mut glib::ffi::GError,
180) -> glib::ffi::gboolean {
181 unsafe {
182 let instance = &*(enumerator as *mut T::Instance);
183 let imp = instance.imp();
184 let cancellable = Option::<Cancellable>::from_glib_none(cancellable);
185
186 let res = imp.close(cancellable.as_ref());
187
188 if !error.is_null() {
189 *error = res.1.to_glib_full()
190 }
191
192 res.0.into_glib()
193 }
194}
195
196#[cfg(test)]
197mod tests {
198 use super::*;
202
203 mod imp {
205 use std::cell::Cell;
206
207 use super::*;
208
209 #[derive(Default)]
210 pub struct MyFileEnumerator(Cell<i32>);
211
212 #[glib::object_subclass]
213 impl ObjectSubclass for MyFileEnumerator {
214 const NAME: &'static str = "MyFileEnumerator";
215 type Type = super::MyFileEnumerator;
216 type ParentType = FileEnumerator;
217 }
218
219 impl ObjectImpl for MyFileEnumerator {
220 fn dispose(&self) {
221 let _ = self.obj().close(Cancellable::NONE);
222 }
223 }
224
225 impl FileEnumeratorImpl for MyFileEnumerator {
227 fn next_file(
228 &self,
229 cancellable: Option<&Cancellable>,
230 ) -> Result<Option<FileInfo>, glib::Error> {
231 if cancellable.is_some_and(|c| c.is_cancelled()) {
232 Err(glib::Error::new::<IOErrorEnum>(
233 IOErrorEnum::Cancelled,
234 "Operation was cancelled",
235 ))
236 } else {
237 match self.0.get() {
238 -1 => Err(glib::Error::new::<IOErrorEnum>(
239 IOErrorEnum::Closed,
240 "Enumerator is closed",
241 )),
242 i if i < 3 => {
243 let file_info = FileInfo::new();
244 file_info.set_display_name(&format!("file{i}"));
245 self.0.set(i + 1);
246 Ok(Some(file_info))
247 }
248 _ => Ok(None),
249 }
250 }
251 }
252
253 fn close(&self, cancellable: Option<&Cancellable>) -> (bool, Option<glib::Error>) {
254 if cancellable.is_some_and(|c| c.is_cancelled()) {
255 (
256 false,
257 Some(glib::Error::new::<IOErrorEnum>(
258 IOErrorEnum::Cancelled,
259 "Operation was cancelled",
260 )),
261 )
262 } else {
263 self.0.set(-1);
264 (true, None)
265 }
266 }
267 }
268
269 #[derive(Default)]
270 pub struct MyCustomFileEnumerator;
271
272 #[glib::object_subclass]
273 impl ObjectSubclass for MyCustomFileEnumerator {
274 const NAME: &'static str = "MyCustomFileEnumerator";
275 type Type = super::MyCustomFileEnumerator;
276 type ParentType = super::MyFileEnumerator;
277 }
278
279 impl ObjectImpl for MyCustomFileEnumerator {}
280
281 impl FileEnumeratorImpl for MyCustomFileEnumerator {}
283
284 impl MyFileEnumeratorImpl for MyCustomFileEnumerator {}
285 }
286
287 glib::wrapper! {
288 pub struct MyFileEnumerator(ObjectSubclass<imp::MyFileEnumerator>) @extends FileEnumerator;
289 }
290
291 pub trait MyFileEnumeratorImpl:
292 ObjectImpl + ObjectSubclass<Type: IsA<MyFileEnumerator> + IsA<FileEnumerator>>
293 {
294 }
295
296 unsafe impl<T: MyFileEnumeratorImpl + FileEnumeratorImpl> IsSubclassable<T> for MyFileEnumerator {}
298
299 glib::wrapper! {
300 pub struct MyCustomFileEnumerator(ObjectSubclass<imp::MyCustomFileEnumerator>) @extends MyFileEnumerator, FileEnumerator;
301 }
302
303 #[test]
304 fn file_enumerator_next_file() {
305 let my_custom_file_enumerator = glib::Object::new::<MyCustomFileEnumerator>();
307 let res = my_custom_file_enumerator.next_file(Cancellable::NONE);
308 assert!(res.as_ref().is_ok_and(|res| res.is_some()));
309 let filename = res.unwrap().unwrap().display_name();
310
311 let my_file_enumerator = glib::Object::new::<MyFileEnumerator>();
313 let res = my_file_enumerator.next_file(Cancellable::NONE);
314 assert!(res.as_ref().is_ok_and(|res| res.is_some()));
315 let expected = res.unwrap().unwrap().display_name();
316
317 assert_eq!(filename, expected);
319
320 for res in my_custom_file_enumerator.upcast::<FileEnumerator>() {
322 assert!(res.as_ref().is_ok());
323 let filename = res.unwrap().display_name();
324
325 let res = my_file_enumerator.next_file(Cancellable::NONE);
326 assert!(res.as_ref().is_ok_and(|res| res.is_some()));
327 let expected = res.unwrap().unwrap().display_name();
328
329 assert_eq!(filename, expected);
331 }
332 }
333
334 #[test]
335 fn file_enumerator_close() {
336 let my_custom_file_enumerator = glib::Object::new::<MyCustomFileEnumerator>();
338 let res = my_custom_file_enumerator.next_file(Cancellable::NONE);
339 assert!(res.as_ref().is_ok_and(|res| res.is_some()));
340 let filename = res.unwrap().unwrap().display_name();
341
342 let my_file_enumerator = glib::Object::new::<MyFileEnumerator>();
344 let res = my_file_enumerator.next_file(Cancellable::NONE);
345 assert!(res.as_ref().is_ok_and(|res| res.is_some()));
346 let expected = res.unwrap().unwrap().display_name();
347
348 assert_eq!(filename, expected);
350
351 let res = my_custom_file_enumerator.close(Cancellable::NONE);
353 assert_eq!(res.1, None);
354 let closed = res.0;
355
356 let res = my_file_enumerator.close(Cancellable::NONE);
358 assert_eq!(res.1, None);
359 let expected = res.0;
360
361 assert_eq!(closed, expected);
363
364 let res = my_custom_file_enumerator.next_file(Cancellable::NONE);
366 assert!(res.is_err());
367 let err = res.unwrap_err();
368
369 let res = my_file_enumerator.next_file(Cancellable::NONE);
371 assert!(res.is_err());
372 let expected = res.unwrap_err();
373
374 assert_eq!(err.domain(), expected.domain());
376 assert!(err.matches::<IOErrorEnum>(IOErrorEnum::Closed));
377 assert!(expected.matches::<IOErrorEnum>(IOErrorEnum::Closed));
378 assert_eq!(err.message(), expected.message());
379 }
380
381 #[test]
382 fn file_enumerator_cancel() {
383 let my_custom_file_enumerator = glib::Object::new::<MyCustomFileEnumerator>();
385 let res = my_custom_file_enumerator.next_file(Cancellable::NONE);
386 assert!(res.as_ref().is_ok_and(|res| res.is_some()));
387 let filename = res.unwrap().unwrap().display_name();
388
389 let my_file_enumerator = glib::Object::new::<MyFileEnumerator>();
391 let res = my_file_enumerator.next_file(Cancellable::NONE);
392 assert!(res.as_ref().is_ok_and(|res| res.is_some()));
393 let expected = res.unwrap().unwrap().display_name();
394
395 assert_eq!(filename, expected);
397
398 let cancellable = Cancellable::new();
400 cancellable.cancel();
401 let res = my_custom_file_enumerator.next_file(Some(&cancellable));
402 assert!(res.as_ref().is_err());
403 let err = res.unwrap_err();
404
405 let cancellable = Cancellable::new();
407 cancellable.cancel();
408 let res = my_file_enumerator.next_file(Some(&cancellable));
409 assert!(res.as_ref().is_err());
410 let expected = res.unwrap_err();
411
412 assert_eq!(err.domain(), expected.domain());
414 assert!(err.matches::<IOErrorEnum>(IOErrorEnum::Cancelled));
415 assert!(expected.matches::<IOErrorEnum>(IOErrorEnum::Cancelled));
416 assert_eq!(err.message(), expected.message());
417
418 let cancellable = Cancellable::new();
420 cancellable.cancel();
421 let res = my_custom_file_enumerator.close(Some(&cancellable));
422 assert!(res.1.is_some());
423 let err = res.1.unwrap();
424
425 let cancellable = Cancellable::new();
427 cancellable.cancel();
428 let res = my_file_enumerator.close(Some(&cancellable));
429 assert!(res.1.is_some());
430 let expected = res.1.unwrap();
431
432 assert_eq!(err.domain(), expected.domain());
434 assert!(err.matches::<IOErrorEnum>(IOErrorEnum::Cancelled));
435 assert!(expected.matches::<IOErrorEnum>(IOErrorEnum::Cancelled));
436 assert_eq!(err.message(), expected.message());
437 }
438}