1/*
2 * Copyright © 2019 Benjamin Otte
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2.1 of the License, or (at your option) any later version.
8 *
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Lesser General Public License for more details.
13 *
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
16 *
17 * Authors: Benjamin Otte <otte@gnome.org>
18 */
19
20#include "config.h"
21
22#include "gtkdirectorylist.h"
23
24#include "gtkintl.h"
25#include "gtkprivate.h"
26
27/**
28 * GtkDirectoryList:
29 *
30 * `GtkDirectoryList` is a list model that wraps g_file_enumerate_children_async().
31 *
32 * It presents a `GListModel` and fills it asynchronously with the `GFileInfo`s
33 * returned from that function.
34 *
35 * Enumeration will start automatically when a the
36 * [property@Gtk.DirectoryList:file] property is set.
37 *
38 * While the `GtkDirectoryList` is being filled, the
39 * [property@Gtk.DirectoryList:loading] property will be set to %TRUE. You can
40 * listen to that property if you want to show information like a `GtkSpinner`
41 * or a "Loading..." text.
42 *
43 * If loading fails at any point, the [property@Gtk.DirectoryList:error]
44 * property will be set to give more indication about the failure.
45 *
46 * The `GFileInfo`s returned from a `GtkDirectoryList` have the "standard::file"
47 * attribute set to the `GFile` they refer to. This way you can get at the file
48 * that is referred to in the same way you would via g_file_enumerator_get_child().
49 * This means you do not need access to the `GtkDirectoryList`, but can access
50 * the `GFile` directly from the `GFileInfo` when operating with a `GtkListView`
51 * or similar.
52 */
53
54/* random number that everyone else seems to use, too */
55#define FILES_PER_QUERY 100
56
57enum {
58 PROP_0,
59 PROP_ATTRIBUTES,
60 PROP_ERROR,
61 PROP_FILE,
62 PROP_IO_PRIORITY,
63 PROP_LOADING,
64 PROP_MONITORED,
65 NUM_PROPERTIES
66};
67
68typedef struct _QueuedEvent QueuedEvent;
69struct _QueuedEvent
70{
71 GtkDirectoryList *list;
72 GFile *file;
73 GFileInfo *info;
74 GFileMonitorEvent event;
75};
76
77static void
78free_queued_event (gpointer data)
79{
80 QueuedEvent *event = data;
81
82 g_clear_object (&event->file);
83 g_clear_object (&event->info);
84 g_free (mem: event);
85}
86
87struct _GtkDirectoryList
88{
89 GObject parent_instance;
90
91 char *attributes;
92 GFile *file;
93 GFileMonitor *monitor;
94 gboolean monitored;
95 int io_priority;
96
97 GCancellable *cancellable;
98 GError *error; /* Error while loading */
99 GSequence *items; /* Use GPtrArray or GListStore here? */
100 GQueue events;
101};
102
103struct _GtkDirectoryListClass
104{
105 GObjectClass parent_class;
106};
107
108static GParamSpec *properties[NUM_PROPERTIES] = { NULL, };
109
110static GType
111gtk_directory_list_get_item_type (GListModel *list)
112{
113 return G_TYPE_FILE_INFO;
114}
115
116static guint
117gtk_directory_list_get_n_items (GListModel *list)
118{
119 GtkDirectoryList *self = GTK_DIRECTORY_LIST (ptr: list);
120
121 return g_sequence_get_length (seq: self->items);
122}
123
124static gpointer
125gtk_directory_list_get_item (GListModel *list,
126 guint position)
127{
128 GtkDirectoryList *self = GTK_DIRECTORY_LIST (ptr: list);
129 GSequenceIter *iter;
130
131 iter = g_sequence_get_iter_at_pos (seq: self->items, pos: position);
132
133 if (g_sequence_iter_is_end (iter))
134 return NULL;
135 else
136 return g_object_ref (g_sequence_get (iter));
137}
138
139static void
140gtk_directory_list_model_init (GListModelInterface *iface)
141{
142 iface->get_item_type = gtk_directory_list_get_item_type;
143 iface->get_n_items = gtk_directory_list_get_n_items;
144 iface->get_item = gtk_directory_list_get_item;
145}
146
147G_DEFINE_TYPE_WITH_CODE (GtkDirectoryList, gtk_directory_list, G_TYPE_OBJECT,
148 G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL, gtk_directory_list_model_init))
149
150static void
151gtk_directory_list_set_property (GObject *object,
152 guint prop_id,
153 const GValue *value,
154 GParamSpec *pspec)
155{
156 GtkDirectoryList *self = GTK_DIRECTORY_LIST (ptr: object);
157
158 switch (prop_id)
159 {
160 case PROP_ATTRIBUTES:
161 gtk_directory_list_set_attributes (self, attributes: g_value_get_string (value));
162 break;
163 case PROP_FILE:
164 gtk_directory_list_set_file (self, file: g_value_get_object (value));
165 break;
166
167 case PROP_IO_PRIORITY:
168 gtk_directory_list_set_io_priority (self, io_priority: g_value_get_int (value));
169 break;
170
171 case PROP_MONITORED:
172 gtk_directory_list_set_monitored (self, monitored: g_value_get_boolean (value));
173 break;
174
175 default:
176 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
177 break;
178 }
179}
180
181static void
182gtk_directory_list_get_property (GObject *object,
183 guint prop_id,
184 GValue *value,
185 GParamSpec *pspec)
186{
187 GtkDirectoryList *self = GTK_DIRECTORY_LIST (ptr: object);
188
189 switch (prop_id)
190 {
191 case PROP_ATTRIBUTES:
192 g_value_set_string (value, v_string: self->attributes);
193 break;
194
195 case PROP_ERROR:
196 g_value_set_boxed (value, v_boxed: self->error);
197 break;
198
199 case PROP_FILE:
200 g_value_set_object (value, v_object: self->file);
201 break;
202
203 case PROP_IO_PRIORITY:
204 g_value_set_int (value, v_int: self->io_priority);
205 break;
206
207 case PROP_LOADING:
208 g_value_set_boolean (value, v_boolean: gtk_directory_list_is_loading (self));
209 break;
210
211 case PROP_MONITORED:
212 g_value_set_boolean (value, v_boolean: gtk_directory_list_get_monitored (self));
213 break;
214
215 default:
216 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
217 break;
218 }
219}
220
221static gboolean
222gtk_directory_list_stop_loading (GtkDirectoryList *self)
223{
224 if (self->cancellable == NULL)
225 return FALSE;
226
227 g_cancellable_cancel (cancellable: self->cancellable);
228 g_clear_object (&self->cancellable);
229 return TRUE;
230}
231
232static void directory_changed (GFileMonitor *monitor,
233 GFile *file,
234 GFile *other_file,
235 GFileMonitorEvent event,
236 gpointer data);
237
238static void
239gtk_directory_list_stop_monitoring (GtkDirectoryList *self)
240{
241 if (self->monitor)
242 g_signal_handlers_disconnect_by_func (self->monitor, directory_changed, self);
243 g_clear_object (&self->monitor);
244}
245
246static void
247gtk_directory_list_dispose (GObject *object)
248{
249 GtkDirectoryList *self = GTK_DIRECTORY_LIST (ptr: object);
250
251 gtk_directory_list_stop_loading (self);
252 gtk_directory_list_stop_monitoring (self);
253
254 g_clear_object (&self->file);
255 g_clear_pointer (&self->attributes, g_free);
256
257 g_clear_error (err: &self->error);
258 g_clear_pointer (&self->items, g_sequence_free);
259
260 g_queue_foreach (queue: &self->events, func: (GFunc) free_queued_event, NULL);
261 g_queue_clear (queue: &self->events);
262
263 G_OBJECT_CLASS (gtk_directory_list_parent_class)->dispose (object);
264}
265
266static void
267gtk_directory_list_class_init (GtkDirectoryListClass *class)
268{
269 GObjectClass *gobject_class = G_OBJECT_CLASS (class);
270
271 gobject_class->set_property = gtk_directory_list_set_property;
272 gobject_class->get_property = gtk_directory_list_get_property;
273 gobject_class->dispose = gtk_directory_list_dispose;
274
275 /**
276 * GtkDirectoryList:attributes: (attributes org.gtk.Property.get=gtk_directory_list_get_attributes org.gtk.Property.set=gtk_directory_list_set_attributes)
277 *
278 * The attributes to query.
279 */
280 properties[PROP_ATTRIBUTES] =
281 g_param_spec_string (name: "attributes",
282 P_("attributes"),
283 P_("Attributes to query"),
284 NULL,
285 GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
286
287 /**
288 * GtkDirectoryList:error: (attributes org.gtk.Property.get=gtk_directory_list_get_error)
289 *
290 * Error encountered while loading files.
291 */
292 properties[PROP_ERROR] =
293 g_param_spec_boxed (name: "error",
294 P_("error"),
295 P_("Error encountered while loading files"),
296 G_TYPE_ERROR,
297 GTK_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY);
298
299 /**
300 * GtkDirectoryList:file: (attributes org.gtk.Property.get=gtk_directory_list_get_file org.gtk.Property.set=gtk_directory_list_set_file)
301 *
302 * File to query.
303 */
304 properties[PROP_FILE] =
305 g_param_spec_object (name: "file",
306 P_("File"),
307 P_("The file to query"),
308 G_TYPE_FILE,
309 GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
310
311 /**
312 * GtkDirectoryList:io-priority: (attributes org.gtk.Property.get=gtk_directory_list_get_io_priority org.gtk.Property.set=gtk_directory_list_set_io_priority)
313 *
314 * Priority used when loading.
315 */
316 properties[PROP_IO_PRIORITY] =
317 g_param_spec_int (name: "io-priority",
318 P_("IO priority"),
319 P_("Priority used when loading"),
320 minimum: -G_MAXINT, G_MAXINT, G_PRIORITY_DEFAULT,
321 GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
322
323 /**
324 * GtkDirectoryList:loading: (attributes org.gtk.Property.get=gtk_directory_list_is_loading)
325 *
326 * %TRUE if files are being loaded.
327 */
328 properties[PROP_LOADING] =
329 g_param_spec_boolean (name: "loading",
330 P_("loading"),
331 P_("TRUE if files are being loaded"),
332 FALSE,
333 GTK_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY);
334
335 /**
336 * GtkDirectoryList:monitored: (attributes org.gtk.Property.get=gtk_directory_list_get_monitored org.gtk.Property.set=gtk_directory_list_set_monitored)
337 *
338 * %TRUE if the directory is monitored for changed.
339 */
340 properties[PROP_MONITORED] =
341 g_param_spec_boolean (name: "monitored",
342 P_("monitored"),
343 P_("TRUE if the directory is monitored for changes"),
344 TRUE,
345 GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
346
347 g_object_class_install_properties (oclass: gobject_class, n_pspecs: NUM_PROPERTIES, pspecs: properties);
348}
349
350static void
351gtk_directory_list_init (GtkDirectoryList *self)
352{
353 self->items = g_sequence_new (data_destroy: g_object_unref);
354 self->io_priority = G_PRIORITY_DEFAULT;
355 self->monitored = TRUE;
356 g_queue_init (queue: &self->events);
357}
358
359/**
360 * gtk_directory_list_new:
361 * @file: (nullable): The file to query
362 * @attributes: (nullable): The attributes to query with
363 *
364 * Creates a new `GtkDirectoryList`.
365 *
366 * The `GtkDirectoryList` is querying the given @file
367 * with the given @attributes.
368 *
369 * Returns: a new `GtkDirectoryList`
370 **/
371GtkDirectoryList *
372gtk_directory_list_new (const char *attributes,
373 GFile *file)
374{
375 g_return_val_if_fail (file == NULL || G_IS_FILE (file), NULL);
376
377 return g_object_new (GTK_TYPE_DIRECTORY_LIST,
378 first_property_name: "attributes", attributes,
379 "file", file,
380 NULL);
381}
382
383static void
384gtk_directory_list_clear_items (GtkDirectoryList *self)
385{
386 guint n_items;
387
388 n_items = g_sequence_get_length (seq: self->items);
389 if (n_items > 0)
390 {
391 g_sequence_remove_range (begin: g_sequence_get_begin_iter (seq: self->items),
392 end: g_sequence_get_end_iter (seq: self->items));
393
394 g_list_model_items_changed (list: G_LIST_MODEL (ptr: self), position: 0, removed: n_items, added: 0);
395 }
396
397 if (self->error)
398 {
399 g_clear_error (err: &self->error);
400 g_object_notify_by_pspec (G_OBJECT (self), pspec: properties[PROP_ERROR]);
401 }
402}
403
404static void
405gtk_directory_list_enumerator_closed_cb (GObject *source,
406 GAsyncResult *res,
407 gpointer user_data)
408{
409 g_file_enumerator_close_finish (G_FILE_ENUMERATOR (source), result: res, NULL);
410}
411
412static void
413gtk_directory_list_got_files_cb (GObject *source,
414 GAsyncResult *res,
415 gpointer user_data)
416{
417 GtkDirectoryList *self = user_data; /* invalid if cancelled */
418 GFileEnumerator *enumerator = G_FILE_ENUMERATOR (source);
419 GError *error = NULL;
420 GList *l, *files;
421 guint n;
422
423 files = g_file_enumerator_next_files_finish (enumerator, result: res, error: &error);
424
425 if (files == NULL)
426 {
427 if (g_error_matches (error, G_IO_ERROR, code: G_IO_ERROR_CANCELLED))
428 {
429 g_clear_error (err: &error);
430 return;
431 }
432
433 g_file_enumerator_close_async (enumerator,
434 io_priority: self->io_priority,
435 NULL,
436 callback: gtk_directory_list_enumerator_closed_cb,
437 NULL);
438
439 g_object_freeze_notify (G_OBJECT (self));
440
441 g_clear_object (&self->cancellable);
442 g_object_notify_by_pspec (G_OBJECT (self), pspec: properties[PROP_LOADING]);
443
444 if (error)
445 {
446 self->error = error;
447 g_object_notify_by_pspec (G_OBJECT (self), pspec: properties[PROP_ERROR]);
448 }
449
450 g_object_thaw_notify (G_OBJECT (self));
451 return;
452 }
453
454 n = 0;
455 for (l = files; l; l = l->next)
456 {
457 GFileInfo *info;
458 GFile *file;
459
460 info = l->data;
461 file = g_file_enumerator_get_child (enumerator, info);
462 g_file_info_set_attribute_object (info, attribute: "standard::file", G_OBJECT (file));
463 g_object_unref (object: file);
464 g_sequence_append (seq: self->items, data: info);
465 n++;
466 }
467 g_list_free (list: files);
468
469 g_file_enumerator_next_files_async (enumerator,
470 num_files: g_file_is_native (file: self->file) ? 50 * FILES_PER_QUERY : FILES_PER_QUERY,
471 io_priority: self->io_priority,
472 cancellable: self->cancellable,
473 callback: gtk_directory_list_got_files_cb,
474 user_data: self);
475
476 if (n > 0)
477 g_list_model_items_changed (list: G_LIST_MODEL (ptr: self), position: g_sequence_get_length (seq: self->items) - n, removed: 0, added: n);
478}
479
480static void
481gtk_directory_list_got_enumerator_cb (GObject *source,
482 GAsyncResult *res,
483 gpointer user_data)
484{
485 GtkDirectoryList *self = user_data; /* invalid if cancelled */
486 GFile *file = G_FILE (source);
487 GFileEnumerator *enumerator;
488 GError *error = NULL;
489
490 enumerator = g_file_enumerate_children_finish (file, res, error: &error);
491 if (enumerator == NULL)
492 {
493 if (g_error_matches (error, G_IO_ERROR, code: G_IO_ERROR_CANCELLED))
494 {
495 g_clear_error (err: &error);
496 return;
497 }
498
499 g_object_freeze_notify (G_OBJECT (self));
500 self->error = error;
501 g_clear_object (&self->cancellable);
502 g_object_notify_by_pspec (G_OBJECT (self), pspec: properties[PROP_LOADING]);
503 g_object_notify_by_pspec (G_OBJECT (self), pspec: properties[PROP_ERROR]);
504 g_object_thaw_notify (G_OBJECT (self));
505 return;
506 }
507
508 g_file_enumerator_next_files_async (enumerator,
509 num_files: g_file_is_native (file) ? 50 * FILES_PER_QUERY : FILES_PER_QUERY,
510 io_priority: self->io_priority,
511 cancellable: self->cancellable,
512 callback: gtk_directory_list_got_files_cb,
513 user_data: self);
514 g_object_unref (object: enumerator);
515}
516
517static void
518gtk_directory_list_start_loading (GtkDirectoryList *self)
519{
520 gboolean was_loading;
521
522 was_loading = gtk_directory_list_stop_loading (self);
523 gtk_directory_list_clear_items (self);
524
525 if (self->file == NULL)
526 {
527 if (was_loading)
528 g_object_notify_by_pspec (G_OBJECT (self), pspec: properties[PROP_LOADING]);
529 return;
530 }
531
532 self->cancellable = g_cancellable_new ();
533 g_file_enumerate_children_async (file: self->file,
534 attributes: self->attributes,
535 flags: G_FILE_QUERY_INFO_NONE,
536 io_priority: self->io_priority,
537 cancellable: self->cancellable,
538 callback: gtk_directory_list_got_enumerator_cb,
539 user_data: self);
540
541 if (!was_loading)
542 g_object_notify_by_pspec (G_OBJECT (self), pspec: properties[PROP_LOADING]);
543}
544
545static GSequenceIter *
546find_file (GSequence *sequence,
547 GFile *file)
548{
549 GSequenceIter *iter;
550
551 for (iter = g_sequence_get_begin_iter (seq: sequence);
552 !g_sequence_iter_is_end (iter);
553 iter = g_sequence_iter_next (iter))
554 {
555 GFileInfo *item = G_FILE_INFO (g_sequence_get (iter));
556 GFile *f = G_FILE (g_file_info_get_attribute_object (item, "standard::file"));
557
558 if (g_file_equal (file1: f, file2: file))
559 return iter;
560 }
561
562 return NULL;
563}
564
565static gboolean
566handle_event (QueuedEvent *event)
567{
568 GtkDirectoryList *self = event->list;
569 GFile *file = event->file;
570 GFileInfo *info = event->info;
571 GSequenceIter *iter;
572 unsigned int position;
573
574 switch ((int)event->event)
575 {
576 case G_FILE_MONITOR_EVENT_MOVED_IN:
577 case G_FILE_MONITOR_EVENT_CREATED:
578 if (!info)
579 return FALSE;
580
581 g_file_info_set_attribute_object (info, attribute: "standard::file", G_OBJECT (file));
582
583 iter = find_file (sequence: self->items, file);
584 if (iter)
585 {
586 position = g_sequence_iter_get_position (iter);
587 g_sequence_set (iter, g_object_ref (info));
588 g_list_model_items_changed (list: G_LIST_MODEL (ptr: self), position, removed: 1, added: 1);
589 }
590 else
591 {
592 position = g_sequence_get_length (seq: self->items);
593 g_sequence_append (seq: self->items, g_object_ref (info));
594 g_list_model_items_changed (list: G_LIST_MODEL (ptr: self), position, removed: 0, added: 1);
595 }
596 break;
597
598 case G_FILE_MONITOR_EVENT_MOVED_OUT:
599 case G_FILE_MONITOR_EVENT_DELETED:
600 iter = find_file (sequence: self->items, file);
601 if (iter)
602 {
603 position = g_sequence_iter_get_position (iter);
604 g_sequence_remove (iter);
605 g_list_model_items_changed (list: G_LIST_MODEL (ptr: self), position, removed: 1, added: 0);
606 }
607 break;
608
609 case G_FILE_MONITOR_EVENT_ATTRIBUTE_CHANGED:
610 if (!info)
611 return FALSE;
612
613 g_file_info_set_attribute_object (info, attribute: "standard::file", G_OBJECT (file));
614
615 iter = find_file (sequence: self->items, file);
616 if (iter)
617 {
618 position = g_sequence_iter_get_position (iter);
619 g_sequence_set (iter, g_object_ref (info));
620 g_list_model_items_changed (list: G_LIST_MODEL (ptr: self), position, removed: 1, added: 1);
621 }
622 break;
623
624 default:
625 g_assert_not_reached ();
626 }
627
628 return TRUE;
629}
630
631static void
632handle_events (GtkDirectoryList *self)
633{
634 QueuedEvent *event;
635
636 do
637 {
638 event = g_queue_peek_tail (queue: &self->events);
639 if (!event)
640 return;
641
642 if (!handle_event (event))
643 return;
644
645 event = g_queue_pop_tail (queue: &self->events);
646 free_queued_event (data: event);
647 }
648 while (TRUE);
649}
650
651static void
652got_new_file_info_cb (GObject *source,
653 GAsyncResult *res,
654 gpointer data)
655{
656 QueuedEvent *event = data;
657 GtkDirectoryList *self = event->list;
658 GFile *file = event->file;
659
660 event->info = g_file_query_info_finish (file, res, NULL);
661 handle_events (self);
662}
663
664static void
665got_existing_file_info_cb (GObject *source,
666 GAsyncResult *res,
667 gpointer data)
668{
669 QueuedEvent *event = data;
670 GtkDirectoryList *self = event->list;
671 GFile *file = event->file;
672
673 event->info = g_file_query_info_finish (file, res, NULL);
674 handle_events (self);
675}
676
677static void
678directory_changed (GFileMonitor *monitor,
679 GFile *file,
680 GFile *other_file,
681 GFileMonitorEvent event,
682 gpointer data)
683{
684 GtkDirectoryList *self = GTK_DIRECTORY_LIST (ptr: data);
685 QueuedEvent *ev;
686
687 switch (event)
688 {
689 case G_FILE_MONITOR_EVENT_MOVED_IN:
690 case G_FILE_MONITOR_EVENT_CREATED:
691 ev = g_new0 (QueuedEvent, 1);
692 ev->list = self;
693 ev->event = event;
694 ev->file = g_object_ref (file);
695 g_queue_push_head (queue: &self->events, data: ev);
696
697 g_file_query_info_async (file,
698 attributes: self->attributes,
699 flags: G_FILE_QUERY_INFO_NONE,
700 io_priority: self->io_priority,
701 cancellable: self->cancellable,
702 callback: got_new_file_info_cb,
703 user_data: ev);
704 break;
705
706 case G_FILE_MONITOR_EVENT_MOVED_OUT:
707 case G_FILE_MONITOR_EVENT_DELETED:
708 ev = g_new0 (QueuedEvent, 1);
709 ev->list = self;
710 ev->event = event;
711 ev->file = g_object_ref (file);
712 g_queue_push_head (queue: &self->events, data: ev);
713
714 handle_events (self);
715 break;
716
717 case G_FILE_MONITOR_EVENT_ATTRIBUTE_CHANGED:
718 ev = g_new0 (QueuedEvent, 1);
719 ev->list = self;
720 ev->event = event;
721 ev->file = g_object_ref (file);
722 g_queue_push_head (queue: &self->events, data: ev);
723
724 g_file_query_info_async (file,
725 attributes: self->attributes,
726 flags: G_FILE_QUERY_INFO_NONE,
727 io_priority: self->io_priority,
728 cancellable: self->cancellable,
729 callback: got_existing_file_info_cb,
730 user_data: ev);
731 break;
732
733 case G_FILE_MONITOR_EVENT_RENAMED:
734 ev = g_new0 (QueuedEvent, 1);
735 ev->list = self;
736 ev->event = G_FILE_MONITOR_EVENT_DELETED;
737 ev->file = g_object_ref (file);
738 g_queue_push_head (queue: &self->events, data: ev);
739
740 ev = g_new0 (QueuedEvent, 1);
741 ev->list = self;
742 ev->event = G_FILE_MONITOR_EVENT_CREATED;
743 ev->file = g_object_ref (other_file);
744 g_queue_push_head (queue: &self->events, data: ev);
745
746 g_file_query_info_async (file: other_file,
747 attributes: self->attributes,
748 flags: G_FILE_QUERY_INFO_NONE,
749 io_priority: self->io_priority,
750 cancellable: self->cancellable,
751 callback: got_existing_file_info_cb,
752 user_data: ev);
753 break;
754
755 case G_FILE_MONITOR_EVENT_CHANGED:
756 case G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT:
757 case G_FILE_MONITOR_EVENT_PRE_UNMOUNT:
758 case G_FILE_MONITOR_EVENT_UNMOUNTED:
759 case G_FILE_MONITOR_EVENT_MOVED:
760 default:
761 break;
762 }
763}
764
765static void
766gtk_directory_list_start_monitoring (GtkDirectoryList *self)
767{
768 g_assert (self->monitor == NULL);
769 self->monitor = g_file_monitor_directory (file: self->file, flags: G_FILE_MONITOR_WATCH_MOVES, NULL, NULL);
770 g_signal_connect (self->monitor, "changed", G_CALLBACK (directory_changed), self);
771}
772
773static void
774gtk_directory_list_update_monitoring (GtkDirectoryList *self)
775{
776 gtk_directory_list_stop_monitoring (self);
777 if (self->file && self->monitored)
778 gtk_directory_list_start_monitoring (self);
779}
780
781/**
782 * gtk_directory_list_set_file: (attributes org.gtk.Method.set_property=file)
783 * @self: a `GtkDirectoryList`
784 * @file: (nullable): the `GFile` to be enumerated
785 *
786 * Sets the @file to be enumerated and starts the enumeration.
787 *
788 * If @file is %NULL, the result will be an empty list.
789 */
790void
791gtk_directory_list_set_file (GtkDirectoryList *self,
792 GFile *file)
793{
794 g_return_if_fail (GTK_IS_DIRECTORY_LIST (self));
795 g_return_if_fail (file == NULL || G_IS_FILE (file));
796
797 if (self->file == file ||
798 (self->file && file && g_file_equal (file1: self->file, file2: file)))
799 return;
800
801 g_object_freeze_notify (G_OBJECT (self));
802
803 g_set_object (&self->file, file);
804
805 gtk_directory_list_update_monitoring (self);
806 gtk_directory_list_start_loading (self);
807
808 g_object_notify_by_pspec (G_OBJECT (self), pspec: properties[PROP_FILE]);
809
810 g_object_thaw_notify (G_OBJECT (self));
811}
812
813/**
814 * gtk_directory_list_get_file: (attributes org.gtk.Method.get_property=file)
815 * @self: a `GtkDirectoryList`
816 *
817 * Gets the file whose children are currently enumerated.
818 *
819 * Returns: (nullable) (transfer none): The file whose children are enumerated
820 **/
821GFile *
822gtk_directory_list_get_file (GtkDirectoryList *self)
823{
824 g_return_val_if_fail (GTK_IS_DIRECTORY_LIST (self), NULL);
825
826 return self->file;
827}
828
829/**
830 * gtk_directory_list_set_attributes: (attributes org.gtk.Method.set_property=attributes)
831 * @self: a `GtkDirectoryList`
832 * @attributes: (nullable): the attributes to enumerate
833 *
834 * Sets the @attributes to be enumerated and starts the enumeration.
835 *
836 * If @attributes is %NULL, no attributes will be queried, but a list
837 * of `GFileInfo`s will still be created.
838 */
839void
840gtk_directory_list_set_attributes (GtkDirectoryList *self,
841 const char *attributes)
842{
843 g_return_if_fail (GTK_IS_DIRECTORY_LIST (self));
844
845 if (self->attributes == attributes)
846 return;
847
848 g_object_freeze_notify (G_OBJECT (self));
849
850 g_free (mem: self->attributes);
851 self->attributes = g_strdup (str: attributes);
852
853 gtk_directory_list_start_loading (self);
854
855 g_object_notify_by_pspec (G_OBJECT (self), pspec: properties[PROP_ATTRIBUTES]);
856
857 g_object_thaw_notify (G_OBJECT (self));
858}
859
860/**
861 * gtk_directory_list_get_attributes: (attributes org.gtk.Method.get_property=attributes)
862 * @self: a `GtkDirectoryList`
863 *
864 * Gets the attributes queried on the children.
865 *
866 * Returns: (nullable) (transfer none): The queried attributes
867 */
868const char *
869gtk_directory_list_get_attributes (GtkDirectoryList *self)
870{
871 g_return_val_if_fail (GTK_IS_DIRECTORY_LIST (self), NULL);
872
873 return self->attributes;
874}
875
876/**
877 * gtk_directory_list_set_io_priority: (attributes org.gtk.Method.set_property=io-priority)
878 * @self: a `GtkDirectoryList`
879 * @io_priority: IO priority to use
880 *
881 * Sets the IO priority to use while loading directories.
882 *
883 * Setting the priority while @self is loading will reprioritize the
884 * ongoing load as soon as possible.
885 *
886 * The default IO priority is %G_PRIORITY_DEFAULT, which is higher than
887 * the GTK redraw priority. If you are loading a lot of directories in
888 * parallel, lowering it to something like %G_PRIORITY_DEFAULT_IDLE
889 * may increase responsiveness.
890 */
891void
892gtk_directory_list_set_io_priority (GtkDirectoryList *self,
893 int io_priority)
894{
895 g_return_if_fail (GTK_IS_DIRECTORY_LIST (self));
896
897 if (self->io_priority == io_priority)
898 return;
899
900 self->io_priority = io_priority;
901
902 g_object_notify_by_pspec (G_OBJECT (self), pspec: properties[PROP_IO_PRIORITY]);
903}
904
905/**
906 * gtk_directory_list_get_io_priority: (attributes org.gtk.Method.get_property=io-priority)
907 * @self: a `GtkDirectoryList`
908 *
909 * Gets the IO priority set via gtk_directory_list_set_io_priority().
910 *
911 * Returns: The IO priority.
912 */
913int
914gtk_directory_list_get_io_priority (GtkDirectoryList *self)
915{
916 g_return_val_if_fail (GTK_IS_DIRECTORY_LIST (self), G_PRIORITY_DEFAULT);
917
918 return self->io_priority;
919}
920
921/**
922 * gtk_directory_list_is_loading: (attributes org.gtk.Method.get_property=loading)
923 * @self: a `GtkDirectoryList`
924 *
925 * Returns %TRUE if the children enumeration is currently in
926 * progress.
927 *
928 * Files will be added to @self from time to time while loading is
929 * going on. The order in which are added is undefined and may change
930 * in between runs.
931 *
932 * Returns: %TRUE if @self is loading
933 */
934gboolean
935gtk_directory_list_is_loading (GtkDirectoryList *self)
936{
937 g_return_val_if_fail (GTK_IS_DIRECTORY_LIST (self), FALSE);
938
939 return self->cancellable != NULL;
940}
941
942/**
943 * gtk_directory_list_get_error: (attributes org.gtk.Method.get_property=error)
944 * @self: a `GtkDirectoryList`
945 *
946 * Gets the loading error, if any.
947 *
948 * If an error occurs during the loading process, the loading process
949 * will finish and this property allows querying the error that happened.
950 * This error will persist until a file is loaded again.
951 *
952 * An error being set does not mean that no files were loaded, and all
953 * successfully queried files will remain in the list.
954 *
955 * Returns: (nullable) (transfer none): The loading error or %NULL if
956 * loading finished successfully
957 */
958const GError *
959gtk_directory_list_get_error (GtkDirectoryList *self)
960{
961 g_return_val_if_fail (GTK_IS_DIRECTORY_LIST (self), FALSE);
962
963 return self->error;
964}
965
966/**
967 * gtk_directory_list_set_monitored: (attributes org.gtk.Method.set_property=monitored)
968 * @self: a `GtkDirectoryList`
969 * @monitored: %TRUE to monitor the directory for changes
970 *
971 * Sets whether the directory list will monitor the directory
972 * for changes.
973 *
974 * If monitoring is enabled, the ::items-changed signal will
975 * be emitted when the directory contents change.
976 *
977 *
978 * When monitoring is turned on after the initial creation
979 * of the directory list, the directory is reloaded to avoid
980 * missing files that appeared between the initial loading
981 * and when monitoring was turned on.
982 */
983void
984gtk_directory_list_set_monitored (GtkDirectoryList *self,
985 gboolean monitored)
986{
987 g_return_if_fail (GTK_IS_DIRECTORY_LIST (self));
988
989 if (self->monitored == monitored)
990 return;
991
992 self->monitored = monitored;
993
994 gtk_directory_list_update_monitoring (self);
995 if (monitored)
996 gtk_directory_list_start_loading (self);
997
998 g_object_notify_by_pspec (G_OBJECT (self), pspec: properties[PROP_MONITORED]);
999}
1000
1001/**
1002 * gtk_directory_list_get_monitored: (attributes org.gtk.Method.get_property=monitored)
1003 * @self: a `GtkDirectoryList`
1004 *
1005 * Returns whether the directory list is monitoring
1006 * the directory for changes.
1007 *
1008 * Returns: %TRUE if the directory is monitored
1009 */
1010gboolean
1011gtk_directory_list_get_monitored (GtkDirectoryList *self)
1012{
1013 g_return_val_if_fail (GTK_IS_DIRECTORY_LIST (self), TRUE);
1014
1015 return self->monitored;
1016}
1017

source code of gtk/gtk/gtkdirectorylist.c