1/*
2 * Copyright © 2020 Red Hat, Inc.
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: Matthias Clasen <mclasen@redhat.com>
18 */
19
20#include "config.h"
21
22#include "gtkbookmarklist.h"
23
24#include "gtksettings.h"
25#include "gtkintl.h"
26#include "gtkprivate.h"
27
28/**
29 * GtkBookmarkList:
30 *
31 * `GtkBookmarkList` is a list model that wraps `GBookmarkFile`.
32 *
33 * It presents a `GListModel` and fills it asynchronously with the
34 * `GFileInfo`s returned from that function.
35 *
36 * The `GFileInfo`s in the list have some attributes in the recent
37 * namespace added: `recent::private` (boolean) and `recent:applications`
38 * (stringv).
39 */
40
41enum {
42 PROP_0,
43 PROP_FILENAME,
44 PROP_ATTRIBUTES,
45 PROP_IO_PRIORITY,
46 PROP_LOADING,
47 NUM_PROPERTIES
48};
49
50struct _GtkBookmarkList
51{
52 GObject parent_instance;
53
54 char *attributes;
55 char *filename;
56 int io_priority;
57 int loading;
58
59 GCancellable *cancellable;
60 GFileMonitor *monitor;
61 GBookmarkFile *file;
62
63 GSequence *items;
64};
65
66struct _GtkBookmarkListClass
67{
68 GObjectClass parent_class;
69};
70
71static GParamSpec *properties[NUM_PROPERTIES] = { NULL, };
72
73static GType
74gtk_bookmark_list_get_item_type (GListModel *list)
75{
76 return G_TYPE_FILE_INFO;
77}
78
79static guint
80gtk_bookmark_list_get_n_items (GListModel *list)
81{
82 GtkBookmarkList *self = GTK_BOOKMARK_LIST (ptr: list);
83
84 return g_sequence_get_length (seq: self->items);
85}
86
87static gpointer
88gtk_bookmark_list_get_item (GListModel *list,
89 guint position)
90{
91 GtkBookmarkList *self = GTK_BOOKMARK_LIST (ptr: list);
92 GSequenceIter *iter;
93
94 iter = g_sequence_get_iter_at_pos (seq: self->items, pos: position);
95
96 if (g_sequence_iter_is_end (iter))
97 return NULL;
98 else
99 return g_object_ref (g_sequence_get (iter));
100}
101
102static void
103gtk_bookmark_list_model_init (GListModelInterface *iface)
104{
105 iface->get_item_type = gtk_bookmark_list_get_item_type;
106 iface->get_n_items = gtk_bookmark_list_get_n_items;
107 iface->get_item = gtk_bookmark_list_get_item;
108}
109
110static void gtk_bookmark_list_start_loading (GtkBookmarkList *self);
111static gboolean gtk_bookmark_list_stop_loading (GtkBookmarkList *self);
112static void bookmark_file_changed (GFileMonitor *monitor,
113 GFile *file,
114 GFile *other_file,
115 GFileMonitorEvent event,
116 gpointer data);
117static void gtk_bookmark_list_set_filename (GtkBookmarkList *self,
118 const char *filename);
119
120G_DEFINE_TYPE_WITH_CODE (GtkBookmarkList, gtk_bookmark_list, G_TYPE_OBJECT,
121 G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL, gtk_bookmark_list_model_init))
122
123static void
124gtk_bookmark_list_set_property (GObject *object,
125 guint prop_id,
126 const GValue *value,
127 GParamSpec *pspec)
128{
129 GtkBookmarkList *self = GTK_BOOKMARK_LIST (ptr: object);
130
131 switch (prop_id)
132 {
133 case PROP_ATTRIBUTES:
134 gtk_bookmark_list_set_attributes (self, attributes: g_value_get_string (value));
135 break;
136
137 case PROP_IO_PRIORITY:
138 gtk_bookmark_list_set_io_priority (self, io_priority: g_value_get_int (value));
139 break;
140
141 case PROP_FILENAME:
142 gtk_bookmark_list_set_filename (self, filename: g_value_get_string (value));
143 break;
144
145 default:
146 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
147 break;
148 }
149}
150
151static void
152gtk_bookmark_list_get_property (GObject *object,
153 guint prop_id,
154 GValue *value,
155 GParamSpec *pspec)
156{
157 GtkBookmarkList *self = GTK_BOOKMARK_LIST (ptr: object);
158
159 switch (prop_id)
160 {
161 case PROP_ATTRIBUTES:
162 g_value_set_string (value, v_string: self->attributes);
163 break;
164
165 case PROP_IO_PRIORITY:
166 g_value_set_int (value, v_int: self->io_priority);
167 break;
168
169 case PROP_FILENAME:
170 g_value_set_string (value, v_string: self->filename);
171 break;
172
173 case PROP_LOADING:
174 g_value_set_boolean (value, v_boolean: gtk_bookmark_list_is_loading (self));
175 break;
176
177 default:
178 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
179 break;
180 }
181}
182
183static void
184gtk_bookmark_list_dispose (GObject *object)
185{
186 GtkBookmarkList *self = GTK_BOOKMARK_LIST (ptr: object);
187
188 gtk_bookmark_list_stop_loading (self);
189
190 g_clear_pointer (&self->attributes, g_free);
191 g_clear_pointer (&self->filename, g_free);
192 g_clear_pointer (&self->items, g_sequence_free);
193 g_clear_pointer (&self->file, g_bookmark_file_free);
194
195 g_signal_handlers_disconnect_by_func (self->monitor, G_CALLBACK (bookmark_file_changed), self);
196 g_clear_object (&self->monitor);
197
198 G_OBJECT_CLASS (gtk_bookmark_list_parent_class)->dispose (object);
199}
200
201static void
202gtk_bookmark_list_class_init (GtkBookmarkListClass *class)
203{
204 GObjectClass *gobject_class = G_OBJECT_CLASS (class);
205
206 gobject_class->set_property = gtk_bookmark_list_set_property;
207 gobject_class->get_property = gtk_bookmark_list_get_property;
208 gobject_class->dispose = gtk_bookmark_list_dispose;
209
210 /**
211 * GtkBookmarkList:filename: (attributes org.gtk.Property.get=gtk_bookmark_list_get_filename)
212 *
213 * The bookmark file to load.
214 */
215 properties[PROP_FILENAME] =
216 g_param_spec_string (name: "filename",
217 P_("Filename"),
218 P_("Bookmark file to load"),
219 NULL,
220 GTK_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_EXPLICIT_NOTIFY);
221 /**
222 * GtkBookmarkList:attributes: (attributes org.gtk.Property.get=gtk_bookmark_list_get_attributes org.gtk.Property.set=gtk_bookmark_list_set_attributes)
223 *
224 * The attributes to query.
225 */
226 properties[PROP_ATTRIBUTES] =
227 g_param_spec_string (name: "attributes",
228 P_("Attributes"),
229 P_("Attributes to query"),
230 NULL,
231 GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
232
233 /**
234 * GtkBookmarkList:io-priority: (attributes org.gtk.Property.get=gtk_bookmark_list_get_io_priority org.gtk.Property.set=gtk_bookmark_list_set_io_priority)
235 *
236 * Priority used when loading.
237 */
238 properties[PROP_IO_PRIORITY] =
239 g_param_spec_int (name: "io-priority",
240 P_("IO priority"),
241 P_("Priority used when loading"),
242 minimum: -G_MAXINT, G_MAXINT, G_PRIORITY_DEFAULT,
243 GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
244
245 /**
246 * GtkBookmarkList:loading: (attributes org.gtk.Property.get=gtk_bookmark_list_is_loading)
247 *
248 * %TRUE if files are being loaded.
249 */
250 properties[PROP_LOADING] =
251 g_param_spec_boolean (name: "loading",
252 P_("loading"),
253 P_("TRUE if files are being loaded"),
254 FALSE,
255 GTK_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY);
256
257 g_object_class_install_properties (oclass: gobject_class, n_pspecs: NUM_PROPERTIES, pspecs: properties);
258}
259
260static void
261gtk_bookmark_list_init (GtkBookmarkList *self)
262{
263 self->items = g_sequence_new (data_destroy: g_object_unref);
264 self->io_priority = G_PRIORITY_DEFAULT;
265 self->file = g_bookmark_file_new ();
266}
267
268static gboolean
269gtk_bookmark_list_stop_loading (GtkBookmarkList *self)
270{
271 if (self->cancellable == NULL)
272 return FALSE;
273
274 g_cancellable_cancel (cancellable: self->cancellable);
275 g_clear_object (&self->cancellable);
276
277 self->loading = 0;
278
279 return TRUE;
280}
281
282static void
283got_file_info (GObject *source,
284 GAsyncResult *res,
285 gpointer user_data)
286{
287 GtkBookmarkList *self = user_data;
288 GFile *file = G_FILE (source);
289 GFileInfo *info;
290 GError *error = NULL;
291
292 info = g_file_query_info_finish (file, res, error: &error);
293 if (g_error_matches (error, G_IO_ERROR, code: G_IO_ERROR_CANCELLED))
294 {
295 g_error_free (error);
296 return;
297 }
298
299 if (info)
300 {
301 char *uri;
302 gboolean is_private;
303 char **apps;
304
305 uri = g_file_get_uri (file);
306 is_private = g_bookmark_file_get_is_private (bookmark: self->file, uri, NULL);
307 apps = g_bookmark_file_get_applications (bookmark: self->file, uri, NULL, NULL);
308
309 g_file_info_set_attribute_object (info, attribute: "standard::file", G_OBJECT (file));
310 g_file_info_set_attribute_boolean (info, attribute: "recent::private", attr_value: is_private);
311 g_file_info_set_attribute_stringv (info, attribute: "recent::applications", attr_value: apps);
312
313 g_strfreev (str_array: apps);
314
315 g_sequence_append (seq: self->items, data: info);
316 g_list_model_items_changed (list: G_LIST_MODEL (ptr: self), position: g_sequence_get_length (seq: self->items) - 1, removed: 0, added: 1);
317
318 g_free (mem: uri);
319 }
320
321 self->loading--;
322
323 if (self->loading == 0)
324 {
325 g_clear_object (&self->cancellable);
326 g_object_notify_by_pspec (G_OBJECT (self), pspec: properties[PROP_LOADING]);
327 }
328}
329
330static void
331gtk_bookmark_list_clear_items (GtkBookmarkList *self)
332{
333 guint n_items;
334
335 n_items = g_sequence_get_length (seq: self->items);
336 if (n_items > 0)
337 {
338 g_sequence_remove_range (begin: g_sequence_get_begin_iter (seq: self->items),
339 end: g_sequence_get_end_iter (seq: self->items));
340
341 g_list_model_items_changed (list: G_LIST_MODEL (ptr: self), position: 0, removed: n_items, added: 0);
342 }
343}
344
345static void
346gtk_bookmark_list_start_loading (GtkBookmarkList *self)
347{
348 gboolean was_loading;
349 GError *error = NULL;
350
351 was_loading = gtk_bookmark_list_stop_loading (self);
352 gtk_bookmark_list_clear_items (self);
353
354 if (g_bookmark_file_load_from_file (bookmark: self->file, filename: self->filename, error: &error))
355 {
356 char **uris;
357 gsize len;
358 int i;
359
360 uris = g_bookmark_file_get_uris (bookmark: self->file, length: &len);
361 if (len > 0)
362 {
363 self->cancellable = g_cancellable_new ();
364 self->loading = len;
365 }
366
367 for (i = 0; i < len; i++)
368 {
369 const char *uri = uris[i];
370 GFile *file;
371
372 /* add this item */
373 file = g_file_new_for_uri (uri);
374 g_file_query_info_async (file,
375 attributes: self->attributes,
376 flags: 0,
377 io_priority: self->io_priority,
378 cancellable: self->cancellable,
379 callback: got_file_info,
380 user_data: self);
381 g_object_unref (object: file);
382 }
383
384 g_strfreev (str_array: uris);
385 }
386 else
387 {
388 if (!g_error_matches (error, G_FILE_ERROR, code: G_FILE_ERROR_NOENT))
389 g_warning ("Failed to load %s: %s", self->filename, error->message);
390 g_clear_error (err: &error);
391 }
392
393 if (was_loading != (self->cancellable != NULL))
394 g_object_notify_by_pspec (G_OBJECT (self), pspec: properties[PROP_LOADING]);
395}
396
397static void
398bookmark_file_changed (GFileMonitor *monitor,
399 GFile *file,
400 GFile *other_file,
401 GFileMonitorEvent event_type,
402 gpointer data)
403{
404 GtkBookmarkList *self = data;
405
406 switch (event_type)
407 {
408 case G_FILE_MONITOR_EVENT_CHANGED:
409 case G_FILE_MONITOR_EVENT_CREATED:
410 case G_FILE_MONITOR_EVENT_DELETED:
411 gtk_bookmark_list_start_loading (self);
412 break;
413
414 case G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT:
415 case G_FILE_MONITOR_EVENT_ATTRIBUTE_CHANGED:
416 case G_FILE_MONITOR_EVENT_PRE_UNMOUNT:
417 case G_FILE_MONITOR_EVENT_UNMOUNTED:
418 case G_FILE_MONITOR_EVENT_MOVED:
419 case G_FILE_MONITOR_EVENT_RENAMED:
420 case G_FILE_MONITOR_EVENT_MOVED_IN:
421 case G_FILE_MONITOR_EVENT_MOVED_OUT:
422
423 default:
424 break;
425 }
426}
427
428static void
429gtk_bookmark_list_set_filename (GtkBookmarkList *self,
430 const char *filename)
431{
432 GFile *file;
433
434 if (filename)
435 self->filename = g_strdup (str: filename);
436 else
437 self->filename = g_build_filename (first_element: g_get_user_data_dir (), "recently-used.xbel", NULL);
438
439 file = g_file_new_for_path (path: self->filename);
440 self->monitor = g_file_monitor_file (file, flags: G_FILE_MONITOR_NONE, NULL, NULL);
441 g_signal_connect (self->monitor, "changed",
442 G_CALLBACK (bookmark_file_changed), self);
443 g_object_unref (object: file);
444
445 gtk_bookmark_list_start_loading (self);
446}
447
448/**
449 * gtk_bookmark_list_get_filename: (attributes org.gtk.Method.get_property=filename)
450 * @self: a `GtkBookmarkList`
451 *
452 * Returns the filename of the bookmark file that
453 * this list is loading.
454 *
455 * Returns: (type filename): the filename of the .xbel file
456 */
457const char *
458gtk_bookmark_list_get_filename (GtkBookmarkList *self)
459{
460 g_return_val_if_fail (GTK_IS_BOOKMARK_LIST (self), NULL);
461
462 return self->filename;
463}
464
465/**
466 * gtk_bookmark_list_new:
467 * @filename: (type filename) (nullable): The bookmark file to load
468 * @attributes: (nullable): The attributes to query
469 *
470 * Creates a new `GtkBookmarkList` with the given @attributes.
471 *
472 * Returns: a new `GtkBookmarkList`
473 */
474GtkBookmarkList *
475gtk_bookmark_list_new (const char *filename,
476 const char *attributes)
477{
478 return g_object_new (GTK_TYPE_BOOKMARK_LIST,
479 first_property_name: "filename", filename,
480 "attributes", attributes,
481 NULL);
482}
483
484/**
485 * gtk_bookmark_list_set_attributes: (attributes org.gtk.Method.set_property=attributes)
486 * @self: a `GtkBookmarkList`
487 * @attributes: (nullable): the attributes to enumerate
488 *
489 * Sets the @attributes to be enumerated and starts the enumeration.
490 *
491 * If @attributes is %NULL, no attributes will be queried, but a list
492 * of `GFileInfo`s will still be created.
493 */
494void
495gtk_bookmark_list_set_attributes (GtkBookmarkList *self,
496 const char *attributes)
497{
498 g_return_if_fail (GTK_IS_BOOKMARK_LIST (self));
499
500 if (g_strcmp0 (str1: self->attributes, str2: attributes) == 0)
501 return;
502
503 g_object_freeze_notify (G_OBJECT (self));
504
505 g_free (mem: self->attributes);
506 self->attributes = g_strdup (str: attributes);
507
508 gtk_bookmark_list_start_loading (self);
509
510 g_object_notify_by_pspec (G_OBJECT (self), pspec: properties[PROP_ATTRIBUTES]);
511
512 g_object_thaw_notify (G_OBJECT (self));
513}
514
515/**
516 * gtk_bookmark_list_get_attributes: (attributes org.gtk.Method.get_property=attributes)
517 * @self: a `GtkBookmarkList`
518 *
519 * Gets the attributes queried on the children.
520 *
521 * Returns: (nullable) (transfer none): The queried attributes
522 */
523const char *
524gtk_bookmark_list_get_attributes (GtkBookmarkList *self)
525{
526 g_return_val_if_fail (GTK_IS_BOOKMARK_LIST (self), NULL);
527
528 return self->attributes;
529}
530
531/**
532 * gtk_bookmark_list_set_io_priority: (attributes org.gtk.Method.set_property=io-priority)
533 * @self: a `GtkBookmarkList`
534 * @io_priority: IO priority to use
535 *
536 * Sets the IO priority to use while loading files.
537 *
538 * The default IO priority is %G_PRIORITY_DEFAULT.
539 */
540void
541gtk_bookmark_list_set_io_priority (GtkBookmarkList *self,
542 int io_priority)
543{
544 g_return_if_fail (GTK_IS_BOOKMARK_LIST (self));
545
546 if (self->io_priority == io_priority)
547 return;
548
549 self->io_priority = io_priority;
550
551 g_object_notify_by_pspec (G_OBJECT (self), pspec: properties[PROP_IO_PRIORITY]);
552}
553
554/**
555 * gtk_bookmark_list_get_io_priority: (attributes org.gtk.Method.get_property=io-priority)
556 * @self: a `GtkBookmarkList`
557 *
558 * Gets the IO priority to use while loading file.
559 *
560 * Returns: The IO priority.
561 */
562int
563gtk_bookmark_list_get_io_priority (GtkBookmarkList *self)
564{
565 g_return_val_if_fail (GTK_IS_BOOKMARK_LIST (self), G_PRIORITY_DEFAULT);
566
567 return self->io_priority;
568}
569
570/**
571 * gtk_bookmark_list_is_loading: (attributes org.gtk.Method.get_property=loading)
572 * @self: a `GtkBookmarkList`
573 *
574 * Returns %TRUE if the files are currently being loaded.
575 *
576 * Files will be added to @self from time to time while loading is
577 * going on. The order in which are added is undefined and may change
578 * in between runs.
579 *
580 * Returns: %TRUE if @self is loading
581 */
582gboolean
583gtk_bookmark_list_is_loading (GtkBookmarkList *self)
584{
585 g_return_val_if_fail (GTK_IS_BOOKMARK_LIST (self), FALSE);
586
587 return self->cancellable != NULL;
588}
589

source code of gtk/gtk/gtkbookmarklist.c