1 | /* GTK - The GIMP Toolkit |
2 | * gtkrecentmanager.c: a manager for the recently used resources |
3 | * |
4 | * Copyright (C) 2006 Emmanuele Bassi |
5 | * |
6 | * This library is free software; you can redistribute it and/or |
7 | * modify it under the terms of the GNU Library General Public |
8 | * License as published by the Free Software Foundation; either |
9 | * version 2 of the License, or (at your option) any later version. |
10 | * |
11 | * This library is distributed in the hope that it will be useful, |
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
14 | * Library General Public License for more details. |
15 | * |
16 | * You should have received a copy of the GNU Library General Public |
17 | * License along with this library. If not, see <http://www.gnu.org/licenses/>. |
18 | */ |
19 | |
20 | /** |
21 | * GtkRecentManager: |
22 | * |
23 | * `GtkRecentManager` manages and looks up recently used files. |
24 | * |
25 | * Each recently used file is identified by its URI, and has meta-data |
26 | * associated to it, like the names and command lines of the applications |
27 | * that have registered it, the number of time each application has |
28 | * registered the same file, the mime type of the file and whether |
29 | * the file should be displayed only by the applications that have |
30 | * registered it. |
31 | * |
32 | * The recently used files list is per user. |
33 | * |
34 | * `GtkRecentManager` acts like a database of all the recently |
35 | * used files. You can create new `GtkRecentManager` objects, but |
36 | * it is more efficient to use the default manager created by GTK. |
37 | * |
38 | * Adding a new recently used file is as simple as: |
39 | * |
40 | * ```c |
41 | * GtkRecentManager *manager; |
42 | * |
43 | * manager = gtk_recent_manager_get_default (); |
44 | * gtk_recent_manager_add_item (manager, file_uri); |
45 | * ``` |
46 | * |
47 | * The `GtkRecentManager` will try to gather all the needed information |
48 | * from the file itself through GIO. |
49 | * |
50 | * Looking up the meta-data associated with a recently used file |
51 | * given its URI requires calling [method@Gtk.RecentManager.lookup_item]: |
52 | * |
53 | * ```c |
54 | * GtkRecentManager *manager; |
55 | * GtkRecentInfo *info; |
56 | * GError *error = NULL; |
57 | * |
58 | * manager = gtk_recent_manager_get_default (); |
59 | * info = gtk_recent_manager_lookup_item (manager, file_uri, &error); |
60 | * if (error) |
61 | * { |
62 | * g_warning ("Could not find the file: %s", error->message); |
63 | * g_error_free (error); |
64 | * } |
65 | * else |
66 | * { |
67 | * // Use the info object |
68 | * gtk_recent_info_unref (info); |
69 | * } |
70 | * ``` |
71 | * |
72 | * In order to retrieve the list of recently used files, you can use |
73 | * [method@Gtk.RecentManager.get_items], which returns a list of |
74 | * [struct@Gtk.RecentInfo]. |
75 | * |
76 | * Note that the maximum age of the recently used files list is |
77 | * controllable through the [property@Gtk.Settings:gtk-recent-files-max-age] |
78 | * property. |
79 | */ |
80 | |
81 | #include "config.h" |
82 | |
83 | #include <sys/types.h> |
84 | #include <sys/stat.h> |
85 | #ifdef HAVE_UNISTD_H |
86 | #include <unistd.h> |
87 | #endif |
88 | #include <errno.h> |
89 | #include <string.h> |
90 | #include <stdlib.h> |
91 | #include <glib.h> |
92 | #include <glib/gstdio.h> |
93 | #include <gio/gio.h> |
94 | |
95 | #include "gtkrecentmanager.h" |
96 | #include "gtkintl.h" |
97 | #include "gtksettings.h" |
98 | #include "gtktypebuiltins.h" |
99 | #include "gtkprivate.h" |
100 | #include "gtkmarshalers.h" |
101 | |
102 | /* the file where we store the recently used items */ |
103 | #define GTK_RECENTLY_USED_FILE "recently-used.xbel" |
104 | |
105 | /* return all items by default */ |
106 | #define DEFAULT_LIMIT -1 |
107 | |
108 | /* limit the size of the list */ |
109 | #define MAX_LIST_SIZE 1000 |
110 | |
111 | /* keep in sync with xdgmime */ |
112 | #define GTK_RECENT_DEFAULT_MIME "application/octet-stream" |
113 | |
114 | typedef struct |
115 | { |
116 | char *name; |
117 | char *exec; |
118 | |
119 | guint count; |
120 | |
121 | GDateTime *stamp; |
122 | } RecentAppInfo; |
123 | |
124 | /** |
125 | * GtkRecentInfo: |
126 | * |
127 | * `GtkRecentInfo` contains the metadata associated with an item in the |
128 | * recently used files list. |
129 | */ |
130 | struct _GtkRecentInfo |
131 | { |
132 | char *uri; |
133 | |
134 | char *display_name; |
135 | char *description; |
136 | |
137 | GDateTime *added; |
138 | GDateTime *modified; |
139 | GDateTime *visited; |
140 | |
141 | char *mime_type; |
142 | |
143 | RecentAppInfo *applications; |
144 | int n_applications; |
145 | GHashTable *apps_lookup; |
146 | |
147 | char **groups; |
148 | int n_groups; |
149 | |
150 | gboolean is_private; |
151 | |
152 | int ref_count; |
153 | }; |
154 | |
155 | struct _GtkRecentManagerPrivate |
156 | { |
157 | char *filename; |
158 | |
159 | guint is_dirty : 1; |
160 | |
161 | int size; |
162 | |
163 | GBookmarkFile *recent_items; |
164 | |
165 | GFileMonitor *monitor; |
166 | |
167 | guint changed_timeout; |
168 | guint changed_age; |
169 | }; |
170 | |
171 | enum |
172 | { |
173 | PROP_0, |
174 | |
175 | PROP_FILENAME, |
176 | PROP_LIMIT, |
177 | PROP_SIZE |
178 | }; |
179 | |
180 | static void gtk_recent_manager_dispose (GObject *object); |
181 | static void gtk_recent_manager_finalize (GObject *object); |
182 | static void gtk_recent_manager_set_property (GObject *object, |
183 | guint prop_id, |
184 | const GValue *value, |
185 | GParamSpec *pspec); |
186 | static void gtk_recent_manager_get_property (GObject *object, |
187 | guint prop_id, |
188 | GValue *value, |
189 | GParamSpec *pspec); |
190 | static void gtk_recent_manager_add_item_query_info (GObject *source_object, |
191 | GAsyncResult *res, |
192 | gpointer user_data); |
193 | static void gtk_recent_manager_monitor_changed (GFileMonitor *monitor, |
194 | GFile *file, |
195 | GFile *other_file, |
196 | GFileMonitorEvent event_type, |
197 | gpointer user_data); |
198 | static void gtk_recent_manager_changed (GtkRecentManager *manager); |
199 | static void gtk_recent_manager_real_changed (GtkRecentManager *manager); |
200 | static void gtk_recent_manager_set_filename (GtkRecentManager *manager, |
201 | const char *filename); |
202 | static void gtk_recent_manager_clamp_to_age (GtkRecentManager *manager, |
203 | int age); |
204 | static void gtk_recent_manager_clamp_to_size (GtkRecentManager *manager, |
205 | const int size); |
206 | |
207 | static void gtk_recent_manager_enabled_changed (GtkRecentManager *manager); |
208 | |
209 | |
210 | static void build_recent_items_list (GtkRecentManager *manager); |
211 | static void purge_recent_items_list (GtkRecentManager *manager, |
212 | GError **error); |
213 | |
214 | static GtkRecentInfo *gtk_recent_info_new (const char *uri); |
215 | static void gtk_recent_info_free (GtkRecentInfo *recent_info); |
216 | |
217 | static guint signal_changed = 0; |
218 | |
219 | static GtkRecentManager *recent_manager_singleton = NULL; |
220 | |
221 | G_DEFINE_TYPE_WITH_PRIVATE (GtkRecentManager, gtk_recent_manager, G_TYPE_OBJECT) |
222 | |
223 | /* Test of haystack has the needle prefix, comparing case |
224 | * insensitive. haystack may be UTF-8, but needle must |
225 | * contain only lowercase ascii. |
226 | */ |
227 | static gboolean |
228 | has_case_prefix (const char *haystack, |
229 | const char *needle) |
230 | { |
231 | const char *h, *n; |
232 | |
233 | /* Eat one character at a time. */ |
234 | h = haystack; |
235 | n = needle; |
236 | |
237 | while (*n && *h && *n == g_ascii_tolower (c: *h)) |
238 | { |
239 | n++; |
240 | h++; |
241 | } |
242 | |
243 | return *n == '\0'; |
244 | } |
245 | |
246 | GQuark |
247 | gtk_recent_manager_error_quark (void) |
248 | { |
249 | return g_quark_from_static_string (string: "gtk-recent-manager-error-quark" ); |
250 | } |
251 | |
252 | static void |
253 | gtk_recent_manager_class_init (GtkRecentManagerClass *klass) |
254 | { |
255 | GObjectClass *gobject_class = G_OBJECT_CLASS (klass); |
256 | |
257 | gobject_class->set_property = gtk_recent_manager_set_property; |
258 | gobject_class->get_property = gtk_recent_manager_get_property; |
259 | gobject_class->dispose = gtk_recent_manager_dispose; |
260 | gobject_class->finalize = gtk_recent_manager_finalize; |
261 | |
262 | /** |
263 | * GtkRecentManager:filename: |
264 | * |
265 | * The full path to the file to be used to store and read the |
266 | * recently used resources list |
267 | */ |
268 | g_object_class_install_property (oclass: gobject_class, |
269 | property_id: PROP_FILENAME, |
270 | pspec: g_param_spec_string (name: "filename" , |
271 | P_("Filename" ), |
272 | P_("The full path to the file to be used to store and read the list" ), |
273 | NULL, |
274 | flags: (G_PARAM_CONSTRUCT_ONLY | GTK_PARAM_READWRITE))); |
275 | |
276 | /** |
277 | * GtkRecentManager:size: |
278 | * |
279 | * The size of the recently used resources list. |
280 | */ |
281 | g_object_class_install_property (oclass: gobject_class, |
282 | property_id: PROP_SIZE, |
283 | pspec: g_param_spec_int (name: "size" , |
284 | P_("Size" ), |
285 | P_("The size of the recently used resources list" ), |
286 | minimum: -1, G_MAXINT, default_value: 0, |
287 | GTK_PARAM_READABLE)); |
288 | |
289 | /** |
290 | * GtkRecentManager::changed: |
291 | * @recent_manager: the recent manager |
292 | * |
293 | * Emitted when the current recently used resources manager changes |
294 | * its contents. |
295 | * |
296 | * This can happen either by calling [method@Gtk.RecentManager.add_item] |
297 | * or by another application. |
298 | */ |
299 | signal_changed = |
300 | g_signal_new (I_("changed" ), |
301 | G_TYPE_FROM_CLASS (klass), |
302 | signal_flags: G_SIGNAL_RUN_FIRST, |
303 | G_STRUCT_OFFSET (GtkRecentManagerClass, changed), |
304 | NULL, NULL, |
305 | NULL, |
306 | G_TYPE_NONE, n_params: 0); |
307 | |
308 | klass->changed = gtk_recent_manager_real_changed; |
309 | } |
310 | |
311 | static void |
312 | gtk_recent_manager_init (GtkRecentManager *manager) |
313 | { |
314 | GtkRecentManagerPrivate *priv; |
315 | GtkSettings *settings; |
316 | |
317 | manager->priv = gtk_recent_manager_get_instance_private (self: manager); |
318 | priv = manager->priv; |
319 | |
320 | priv->size = 0; |
321 | priv->filename = NULL; |
322 | |
323 | settings = gtk_settings_get_default (); |
324 | if (settings) |
325 | g_signal_connect_swapped (settings, "notify::gtk-recent-files-enabled" , |
326 | G_CALLBACK (gtk_recent_manager_enabled_changed), manager); |
327 | } |
328 | |
329 | static void |
330 | gtk_recent_manager_set_property (GObject *object, |
331 | guint prop_id, |
332 | const GValue *value, |
333 | GParamSpec *pspec) |
334 | { |
335 | GtkRecentManager *recent_manager = GTK_RECENT_MANAGER (object); |
336 | |
337 | switch (prop_id) |
338 | { |
339 | case PROP_FILENAME: |
340 | gtk_recent_manager_set_filename (manager: recent_manager, filename: g_value_get_string (value)); |
341 | break; |
342 | default: |
343 | G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); |
344 | break; |
345 | } |
346 | } |
347 | |
348 | static void |
349 | gtk_recent_manager_get_property (GObject *object, |
350 | guint prop_id, |
351 | GValue *value, |
352 | GParamSpec *pspec) |
353 | { |
354 | GtkRecentManager *recent_manager = GTK_RECENT_MANAGER (object); |
355 | |
356 | switch (prop_id) |
357 | { |
358 | case PROP_FILENAME: |
359 | g_value_set_string (value, v_string: recent_manager->priv->filename); |
360 | break; |
361 | case PROP_SIZE: |
362 | g_value_set_int (value, v_int: recent_manager->priv->size); |
363 | break; |
364 | default: |
365 | G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); |
366 | break; |
367 | } |
368 | } |
369 | |
370 | static void |
371 | gtk_recent_manager_finalize (GObject *object) |
372 | { |
373 | GtkRecentManager *manager = GTK_RECENT_MANAGER (object); |
374 | GtkRecentManagerPrivate *priv = manager->priv; |
375 | |
376 | g_free (mem: priv->filename); |
377 | |
378 | if (priv->recent_items != NULL) |
379 | g_bookmark_file_free (bookmark: priv->recent_items); |
380 | |
381 | G_OBJECT_CLASS (gtk_recent_manager_parent_class)->finalize (object); |
382 | } |
383 | |
384 | static void |
385 | gtk_recent_manager_dispose (GObject *gobject) |
386 | { |
387 | GtkRecentManager *manager = GTK_RECENT_MANAGER (gobject); |
388 | GtkRecentManagerPrivate *priv = manager->priv; |
389 | |
390 | if (priv->monitor != NULL) |
391 | { |
392 | g_signal_handlers_disconnect_by_func (priv->monitor, |
393 | G_CALLBACK (gtk_recent_manager_monitor_changed), |
394 | manager); |
395 | g_object_unref (object: priv->monitor); |
396 | priv->monitor = NULL; |
397 | } |
398 | |
399 | if (priv->changed_timeout != 0) |
400 | { |
401 | g_source_remove (tag: priv->changed_timeout); |
402 | priv->changed_timeout = 0; |
403 | priv->changed_age = 0; |
404 | } |
405 | |
406 | if (priv->is_dirty) |
407 | { |
408 | g_object_ref (manager); |
409 | g_signal_emit (instance: manager, signal_id: signal_changed, detail: 0); |
410 | g_object_unref (object: manager); |
411 | } |
412 | |
413 | G_OBJECT_CLASS (gtk_recent_manager_parent_class)->dispose (gobject); |
414 | } |
415 | |
416 | static void |
417 | gtk_recent_manager_enabled_changed (GtkRecentManager *manager) |
418 | { |
419 | manager->priv->is_dirty = TRUE; |
420 | gtk_recent_manager_changed (manager); |
421 | } |
422 | |
423 | static void |
424 | gtk_recent_manager_real_changed (GtkRecentManager *manager) |
425 | { |
426 | GtkRecentManagerPrivate *priv = manager->priv; |
427 | |
428 | g_object_freeze_notify (G_OBJECT (manager)); |
429 | |
430 | if (priv->is_dirty) |
431 | { |
432 | GError *write_error; |
433 | |
434 | /* we are marked as dirty, so we dump the content of our |
435 | * recently used items list |
436 | */ |
437 | if (!priv->recent_items) |
438 | { |
439 | /* if no container object has been defined, we create a new |
440 | * empty container, and dump it |
441 | */ |
442 | priv->recent_items = g_bookmark_file_new (); |
443 | priv->size = 0; |
444 | } |
445 | else |
446 | { |
447 | GtkSettings *settings; |
448 | int age; |
449 | int max_size = MAX_LIST_SIZE; |
450 | gboolean enabled; |
451 | |
452 | settings = gtk_settings_get_default (); |
453 | if (settings) |
454 | g_object_get (G_OBJECT (settings), |
455 | first_property_name: "gtk-recent-files-max-age" , &age, |
456 | "gtk-recent-files-enabled" , &enabled, |
457 | NULL); |
458 | else |
459 | { |
460 | age = 30; |
461 | enabled = TRUE; |
462 | } |
463 | |
464 | if (age == 0 || max_size == 0 || !enabled) |
465 | { |
466 | g_bookmark_file_free (bookmark: priv->recent_items); |
467 | priv->recent_items = g_bookmark_file_new (); |
468 | priv->size = 0; |
469 | } |
470 | else |
471 | { |
472 | if (age > 0) |
473 | gtk_recent_manager_clamp_to_age (manager, age); |
474 | if (max_size > 0) |
475 | gtk_recent_manager_clamp_to_size (manager, size: max_size); |
476 | } |
477 | } |
478 | |
479 | if (priv->filename != NULL) |
480 | { |
481 | write_error = NULL; |
482 | g_bookmark_file_to_file (bookmark: priv->recent_items, filename: priv->filename, error: &write_error); |
483 | if (write_error) |
484 | { |
485 | char *utf8 = g_filename_to_utf8 (opsysstring: priv->filename, len: -1, NULL, NULL, NULL); |
486 | g_warning ("Attempting to store changes into '%s', but failed: %s" , |
487 | utf8 ? utf8 : "(invalid filename)" , |
488 | write_error->message); |
489 | g_free (mem: utf8); |
490 | g_error_free (error: write_error); |
491 | } |
492 | |
493 | if (g_chmod (file: priv->filename, mode: 0600) < 0) |
494 | { |
495 | char *utf8 = g_filename_to_utf8 (opsysstring: priv->filename, len: -1, NULL, NULL, NULL); |
496 | g_warning ("Attempting to set the permissions of '%s', but failed: %s" , |
497 | utf8 ? utf8 : "(invalid filename)" , |
498 | g_strerror (errno)); |
499 | g_free (mem: utf8); |
500 | } |
501 | } |
502 | |
503 | /* mark us as clean */ |
504 | priv->is_dirty = FALSE; |
505 | } |
506 | else |
507 | { |
508 | /* we are not marked as dirty, so we have been called |
509 | * because the recently used resources file has been |
510 | * changed (and not from us). |
511 | */ |
512 | build_recent_items_list (manager); |
513 | } |
514 | |
515 | g_object_thaw_notify (G_OBJECT (manager)); |
516 | } |
517 | |
518 | static void |
519 | gtk_recent_manager_monitor_changed (GFileMonitor *monitor, |
520 | GFile *file, |
521 | GFile *other_file, |
522 | GFileMonitorEvent event_type, |
523 | gpointer user_data) |
524 | { |
525 | GtkRecentManager *manager = user_data; |
526 | |
527 | switch (event_type) |
528 | { |
529 | case G_FILE_MONITOR_EVENT_CHANGED: |
530 | case G_FILE_MONITOR_EVENT_CREATED: |
531 | case G_FILE_MONITOR_EVENT_DELETED: |
532 | gtk_recent_manager_changed (manager); |
533 | break; |
534 | |
535 | case G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT: |
536 | case G_FILE_MONITOR_EVENT_ATTRIBUTE_CHANGED: |
537 | case G_FILE_MONITOR_EVENT_PRE_UNMOUNT: |
538 | case G_FILE_MONITOR_EVENT_UNMOUNTED: |
539 | case G_FILE_MONITOR_EVENT_MOVED: |
540 | case G_FILE_MONITOR_EVENT_RENAMED: |
541 | case G_FILE_MONITOR_EVENT_MOVED_IN: |
542 | case G_FILE_MONITOR_EVENT_MOVED_OUT: |
543 | |
544 | default: |
545 | break; |
546 | } |
547 | } |
548 | |
549 | static char * |
550 | get_default_filename (void) |
551 | { |
552 | if (g_mkdir_with_parents (pathname: g_get_user_data_dir (), mode: 0755) == -1) |
553 | { |
554 | int saved_errno = errno; |
555 | |
556 | g_critical ("Unable to create user data directory '%s' for storing " |
557 | "the recently used files list: %s" , |
558 | g_get_user_data_dir (), |
559 | g_strerror (saved_errno)); |
560 | |
561 | return NULL; |
562 | } |
563 | |
564 | return g_build_filename (first_element: g_get_user_data_dir (), |
565 | GTK_RECENTLY_USED_FILE, |
566 | NULL); |
567 | } |
568 | |
569 | static void |
570 | gtk_recent_manager_set_filename (GtkRecentManager *manager, |
571 | const char *filename) |
572 | { |
573 | GtkRecentManagerPrivate *priv; |
574 | GFile *file; |
575 | GError *error; |
576 | |
577 | g_assert (GTK_IS_RECENT_MANAGER (manager)); |
578 | |
579 | priv = manager->priv; |
580 | |
581 | /* if a filename is already set and filename is not NULL, then copy |
582 | * it and reset the monitor; otherwise, if it's NULL we're being |
583 | * called from the finalization sequence, so we simply disconnect |
584 | * the monitoring and return. |
585 | * |
586 | * if no filename is set and filename is NULL, use the default. |
587 | */ |
588 | if (priv->filename) |
589 | { |
590 | g_free (mem: priv->filename); |
591 | |
592 | if (priv->monitor) |
593 | { |
594 | g_signal_handlers_disconnect_by_func (priv->monitor, |
595 | G_CALLBACK (gtk_recent_manager_monitor_changed), |
596 | manager); |
597 | g_object_unref (object: priv->monitor); |
598 | priv->monitor = NULL; |
599 | } |
600 | |
601 | if (!filename || *filename == '\0') |
602 | return; |
603 | else |
604 | priv->filename = g_strdup (str: filename); |
605 | } |
606 | else |
607 | { |
608 | if (!filename || *filename == '\0') |
609 | priv->filename = get_default_filename (); |
610 | else |
611 | priv->filename = g_strdup (str: filename); |
612 | } |
613 | |
614 | if (priv->filename != NULL) |
615 | { |
616 | file = g_file_new_for_path (path: priv->filename); |
617 | |
618 | error = NULL; |
619 | priv->monitor = g_file_monitor_file (file, flags: G_FILE_MONITOR_NONE, NULL, error: &error); |
620 | if (error) |
621 | { |
622 | char *utf8 = g_filename_to_utf8 (opsysstring: priv->filename, len: -1, NULL, NULL, NULL); |
623 | g_warning ("Unable to monitor '%s': %s\n" |
624 | "The GtkRecentManager will not update its contents " |
625 | "if the file is changed from other instances" , |
626 | utf8 ? utf8 : "(invalid filename)" , |
627 | error->message); |
628 | g_free (mem: utf8); |
629 | g_error_free (error); |
630 | } |
631 | else |
632 | g_signal_connect (priv->monitor, "changed" , |
633 | G_CALLBACK (gtk_recent_manager_monitor_changed), |
634 | manager); |
635 | |
636 | g_object_unref (object: file); |
637 | } |
638 | |
639 | build_recent_items_list (manager); |
640 | } |
641 | |
642 | /* reads the recently used resources file and builds the items list. |
643 | * we keep the items list inside the parser object, and build the |
644 | * RecentInfo object only on user’s demand to avoid useless replication. |
645 | * this function resets the dirty bit of the manager. |
646 | */ |
647 | static void |
648 | build_recent_items_list (GtkRecentManager *manager) |
649 | { |
650 | GtkRecentManagerPrivate *priv = manager->priv; |
651 | GError *read_error; |
652 | int size; |
653 | |
654 | if (!priv->recent_items) |
655 | { |
656 | priv->recent_items = g_bookmark_file_new (); |
657 | priv->size = 0; |
658 | } |
659 | |
660 | if (priv->filename != NULL) |
661 | { |
662 | /* the file exists, and it's valid (we hope); if not, destroy the container |
663 | * object and hope for a better result when the next "changed" signal is |
664 | * fired. |
665 | */ |
666 | read_error = NULL; |
667 | g_bookmark_file_load_from_file (bookmark: priv->recent_items, filename: priv->filename, error: &read_error); |
668 | if (read_error) |
669 | { |
670 | /* if the file does not exist we just wait for the first write |
671 | * operation on this recent manager instance, to avoid creating |
672 | * empty files and leading to spurious file system events (Sabayon |
673 | * will not be happy about those) |
674 | */ |
675 | if (read_error->domain == G_FILE_ERROR && |
676 | read_error->code != G_FILE_ERROR_NOENT) |
677 | { |
678 | char *utf8 = g_filename_to_utf8 (opsysstring: priv->filename, len: -1, NULL, NULL, NULL); |
679 | g_warning ("Attempting to read the recently used resources " |
680 | "file at '%s', but the parser failed: %s." , |
681 | utf8 ? utf8 : "(invalid filename)" , |
682 | read_error->message); |
683 | g_free (mem: utf8); |
684 | } |
685 | |
686 | g_bookmark_file_free (bookmark: priv->recent_items); |
687 | priv->recent_items = NULL; |
688 | |
689 | g_error_free (error: read_error); |
690 | } |
691 | else |
692 | { |
693 | size = g_bookmark_file_get_size (bookmark: priv->recent_items); |
694 | if (priv->size != size) |
695 | { |
696 | priv->size = size; |
697 | |
698 | g_object_notify (G_OBJECT (manager), property_name: "size" ); |
699 | } |
700 | } |
701 | } |
702 | |
703 | priv->is_dirty = FALSE; |
704 | } |
705 | |
706 | |
707 | /******************** |
708 | * GtkRecentManager * |
709 | ********************/ |
710 | |
711 | |
712 | /** |
713 | * gtk_recent_manager_new: |
714 | * |
715 | * Creates a new recent manager object. |
716 | * |
717 | * Recent manager objects are used to handle the list of recently used |
718 | * resources. A `GtkRecentManager` object monitors the recently used |
719 | * resources list, and emits the [signal@Gtk.RecentManager::changed] |
720 | * signal each time something inside the list changes. |
721 | * |
722 | * `GtkRecentManager` objects are expensive: be sure to create them |
723 | * only when needed. You should use [func@Gtk.RecentManager.get_default] |
724 | * instead. |
725 | * |
726 | * Returns: A newly created `GtkRecentManager` object |
727 | */ |
728 | GtkRecentManager * |
729 | gtk_recent_manager_new (void) |
730 | { |
731 | return g_object_new (GTK_TYPE_RECENT_MANAGER, NULL); |
732 | } |
733 | |
734 | /** |
735 | * gtk_recent_manager_get_default: |
736 | * |
737 | * Gets a unique instance of `GtkRecentManager` that you can share |
738 | * in your application without caring about memory management. |
739 | * |
740 | * Returns: (transfer none): A unique `GtkRecentManager`. Do not ref or |
741 | * unref it. |
742 | */ |
743 | GtkRecentManager * |
744 | gtk_recent_manager_get_default (void) |
745 | { |
746 | if (G_UNLIKELY (!recent_manager_singleton)) |
747 | recent_manager_singleton = gtk_recent_manager_new (); |
748 | |
749 | return recent_manager_singleton; |
750 | } |
751 | |
752 | |
753 | static void |
754 | gtk_recent_manager_add_item_query_info (GObject *source_object, |
755 | GAsyncResult *res, |
756 | gpointer user_data) |
757 | { |
758 | GFile *file = G_FILE (source_object); |
759 | GtkRecentManager *manager = user_data; |
760 | GtkRecentData recent_data; |
761 | GFileInfo *file_info; |
762 | char *uri, *basename, *content_type; |
763 | |
764 | uri = g_file_get_uri (file); |
765 | |
766 | file_info = g_file_query_info_finish (file, res, NULL); /* NULL-GError */ |
767 | |
768 | recent_data.display_name = NULL; |
769 | recent_data.description = NULL; |
770 | |
771 | if (file_info) |
772 | { |
773 | content_type = g_file_info_get_attribute_as_string (info: file_info, G_FILE_ATTRIBUTE_STANDARD_FAST_CONTENT_TYPE); |
774 | |
775 | if (G_LIKELY (content_type)) |
776 | recent_data.mime_type = g_content_type_get_mime_type (type: content_type); |
777 | else |
778 | recent_data.mime_type = g_strdup (GTK_RECENT_DEFAULT_MIME); |
779 | |
780 | g_free (mem: content_type); |
781 | g_object_unref (object: file_info); |
782 | } |
783 | else |
784 | { |
785 | basename = g_file_get_basename (file); |
786 | content_type = g_content_type_guess (filename: basename, NULL, data_size: 0, NULL); |
787 | recent_data.mime_type = g_content_type_get_mime_type (type: content_type); |
788 | g_free (mem: basename); |
789 | g_free (mem: content_type); |
790 | } |
791 | |
792 | recent_data.app_name = g_strdup (str: g_get_application_name ()); |
793 | recent_data.app_exec = g_strjoin (separator: " " , g_get_prgname (), "%u" , NULL); |
794 | recent_data.groups = NULL; |
795 | recent_data.is_private = FALSE; |
796 | |
797 | /* Ignore return value, this can't fail anyway since all required |
798 | * fields are set |
799 | */ |
800 | gtk_recent_manager_add_full (manager, uri, recent_data: &recent_data); |
801 | |
802 | manager->priv->is_dirty = TRUE; |
803 | gtk_recent_manager_changed (manager); |
804 | |
805 | g_free (mem: recent_data.mime_type); |
806 | g_free (mem: recent_data.app_name); |
807 | g_free (mem: recent_data.app_exec); |
808 | |
809 | g_object_unref (object: manager); |
810 | g_free (mem: uri); |
811 | } |
812 | |
813 | /** |
814 | * gtk_recent_manager_add_item: |
815 | * @manager: a `GtkRecentManager` |
816 | * @uri: a valid URI |
817 | * |
818 | * Adds a new resource, pointed by @uri, into the recently used |
819 | * resources list. |
820 | * |
821 | * This function automatically retrieves some of the needed |
822 | * metadata and setting other metadata to common default values; |
823 | * it then feeds the data to [method@Gtk.RecentManager.add_full]. |
824 | * |
825 | * See [method@Gtk.RecentManager.add_full] if you want to explicitly |
826 | * define the metadata for the resource pointed by @uri. |
827 | * |
828 | * Returns: %TRUE if the new item was successfully added |
829 | * to the recently used resources list |
830 | */ |
831 | gboolean |
832 | gtk_recent_manager_add_item (GtkRecentManager *manager, |
833 | const char *uri) |
834 | { |
835 | GFile* file; |
836 | |
837 | g_return_val_if_fail (GTK_IS_RECENT_MANAGER (manager), FALSE); |
838 | g_return_val_if_fail (uri != NULL, FALSE); |
839 | |
840 | file = g_file_new_for_uri (uri); |
841 | |
842 | g_file_query_info_async (file, |
843 | G_FILE_ATTRIBUTE_STANDARD_FAST_CONTENT_TYPE, |
844 | G_PRIORITY_DEFAULT, |
845 | io_priority: G_FILE_QUERY_INFO_NONE, |
846 | NULL, |
847 | callback: gtk_recent_manager_add_item_query_info, |
848 | g_object_ref (manager)); |
849 | |
850 | g_object_unref (object: file); |
851 | |
852 | return TRUE; |
853 | } |
854 | |
855 | /** |
856 | * gtk_recent_manager_add_full: |
857 | * @manager: a `GtkRecentManager` |
858 | * @uri: a valid URI |
859 | * @recent_data: metadata of the resource |
860 | * |
861 | * Adds a new resource, pointed by @uri, into the recently used |
862 | * resources list, using the metadata specified inside the |
863 | * `GtkRecentData` passed in @recent_data. |
864 | * |
865 | * The passed URI will be used to identify this resource inside the |
866 | * list. |
867 | * |
868 | * In order to register the new recently used resource, metadata about |
869 | * the resource must be passed as well as the URI; the metadata is |
870 | * stored in a `GtkRecentData`, which must contain the MIME |
871 | * type of the resource pointed by the URI; the name of the application |
872 | * that is registering the item, and a command line to be used when |
873 | * launching the item. |
874 | * |
875 | * Optionally, a `GtkRecentData` might contain a UTF-8 string |
876 | * to be used when viewing the item instead of the last component of |
877 | * the URI; a short description of the item; whether the item should |
878 | * be considered private - that is, should be displayed only by the |
879 | * applications that have registered it. |
880 | * |
881 | * Returns: %TRUE if the new item was successfully added to the |
882 | * recently used resources list, %FALSE otherwise |
883 | */ |
884 | gboolean |
885 | gtk_recent_manager_add_full (GtkRecentManager *manager, |
886 | const char *uri, |
887 | const GtkRecentData *data) |
888 | { |
889 | GtkRecentManagerPrivate *priv; |
890 | GtkSettings *settings; |
891 | gboolean enabled; |
892 | |
893 | g_return_val_if_fail (GTK_IS_RECENT_MANAGER (manager), FALSE); |
894 | g_return_val_if_fail (uri != NULL, FALSE); |
895 | g_return_val_if_fail (data != NULL, FALSE); |
896 | |
897 | /* sanity checks */ |
898 | if ((data->display_name) && |
899 | (!g_utf8_validate (str: data->display_name, max_len: -1, NULL))) |
900 | { |
901 | g_warning ("Attempting to add '%s' to the list of recently used " |
902 | "resources, but the display name is not a valid UTF-8 " |
903 | "encoded string" , |
904 | uri); |
905 | return FALSE; |
906 | } |
907 | |
908 | if ((data->description) && |
909 | (!g_utf8_validate (str: data->description, max_len: -1, NULL))) |
910 | { |
911 | g_warning ("Attempting to add '%s' to the list of recently used " |
912 | "resources, but the description is not a valid UTF-8 " |
913 | "encoded string" , |
914 | uri); |
915 | return FALSE; |
916 | } |
917 | |
918 | |
919 | if (!data->mime_type) |
920 | { |
921 | g_warning ("Attempting to add '%s' to the list of recently used " |
922 | "resources, but no MIME type was defined" , |
923 | uri); |
924 | return FALSE; |
925 | } |
926 | |
927 | if (!data->app_name) |
928 | { |
929 | g_warning ("Attempting to add '%s' to the list of recently used " |
930 | "resources, but no name of the application that is " |
931 | "registering it was defined" , |
932 | uri); |
933 | return FALSE; |
934 | } |
935 | |
936 | if (!data->app_exec) |
937 | { |
938 | g_warning ("Attempting to add '%s' to the list of recently used " |
939 | "resources, but no command line for the application " |
940 | "that is registering it was defined" , |
941 | uri); |
942 | return FALSE; |
943 | } |
944 | |
945 | settings = gtk_settings_get_default (); |
946 | g_object_get (G_OBJECT (settings), first_property_name: "gtk-recent-files-enabled" , &enabled, NULL); |
947 | if (!enabled) |
948 | return TRUE; |
949 | |
950 | priv = manager->priv; |
951 | |
952 | if (!priv->recent_items) |
953 | { |
954 | priv->recent_items = g_bookmark_file_new (); |
955 | priv->size = 0; |
956 | } |
957 | |
958 | if (data->display_name) |
959 | g_bookmark_file_set_title (bookmark: priv->recent_items, uri, title: data->display_name); |
960 | |
961 | if (data->description) |
962 | g_bookmark_file_set_description (bookmark: priv->recent_items, uri, description: data->description); |
963 | |
964 | g_bookmark_file_set_mime_type (bookmark: priv->recent_items, uri, mime_type: data->mime_type); |
965 | |
966 | if (data->groups && ((char*)data->groups)[0] != '\0') |
967 | { |
968 | int j; |
969 | |
970 | for (j = 0; (data->groups)[j] != NULL; j++) |
971 | g_bookmark_file_add_group (bookmark: priv->recent_items, uri, group: (data->groups)[j]); |
972 | } |
973 | |
974 | /* register the application; this will take care of updating the |
975 | * registration count and time in case the application has |
976 | * already registered the same document inside the list |
977 | */ |
978 | g_bookmark_file_add_application (bookmark: priv->recent_items, uri, |
979 | name: data->app_name, |
980 | exec: data->app_exec); |
981 | |
982 | g_bookmark_file_set_is_private (bookmark: priv->recent_items, uri, |
983 | is_private: data->is_private); |
984 | |
985 | /* mark us as dirty, so that when emitting the "changed" signal we |
986 | * will dump our changes |
987 | */ |
988 | priv->is_dirty = TRUE; |
989 | gtk_recent_manager_changed (manager); |
990 | |
991 | return TRUE; |
992 | } |
993 | |
994 | /** |
995 | * gtk_recent_manager_remove_item: |
996 | * @manager: a `GtkRecentManager` |
997 | * @uri: the URI of the item you wish to remove |
998 | * @error: (nullable): return location for a `GError` |
999 | * |
1000 | * Removes a resource pointed by @uri from the recently used resources |
1001 | * list handled by a recent manager. |
1002 | * |
1003 | * Returns: %TRUE if the item pointed by @uri has been successfully |
1004 | * removed by the recently used resources list, and %FALSE otherwise |
1005 | */ |
1006 | gboolean |
1007 | gtk_recent_manager_remove_item (GtkRecentManager *manager, |
1008 | const char *uri, |
1009 | GError **error) |
1010 | { |
1011 | GtkRecentManagerPrivate *priv; |
1012 | GError *remove_error = NULL; |
1013 | |
1014 | g_return_val_if_fail (GTK_IS_RECENT_MANAGER (manager), FALSE); |
1015 | g_return_val_if_fail (uri != NULL, FALSE); |
1016 | g_return_val_if_fail (error == NULL || *error == NULL, FALSE); |
1017 | |
1018 | priv = manager->priv; |
1019 | |
1020 | if (!priv->recent_items) |
1021 | { |
1022 | priv->recent_items = g_bookmark_file_new (); |
1023 | priv->size = 0; |
1024 | |
1025 | g_set_error (err: error, GTK_RECENT_MANAGER_ERROR, |
1026 | code: GTK_RECENT_MANAGER_ERROR_NOT_FOUND, |
1027 | _("Unable to find an item with URI “%s”" ), |
1028 | uri); |
1029 | |
1030 | return FALSE; |
1031 | } |
1032 | |
1033 | g_bookmark_file_remove_item (bookmark: priv->recent_items, uri, error: &remove_error); |
1034 | if (remove_error) |
1035 | { |
1036 | g_error_free (error: remove_error); |
1037 | |
1038 | g_set_error (err: error, GTK_RECENT_MANAGER_ERROR, |
1039 | code: GTK_RECENT_MANAGER_ERROR_NOT_FOUND, |
1040 | _("Unable to find an item with URI “%s”" ), |
1041 | uri); |
1042 | |
1043 | return FALSE; |
1044 | } |
1045 | |
1046 | priv->is_dirty = TRUE; |
1047 | gtk_recent_manager_changed (manager); |
1048 | |
1049 | return TRUE; |
1050 | } |
1051 | |
1052 | /** |
1053 | * gtk_recent_manager_has_item: |
1054 | * @manager: a `GtkRecentManager` |
1055 | * @uri: a URI |
1056 | * |
1057 | * Checks whether there is a recently used resource registered |
1058 | * with @uri inside the recent manager. |
1059 | * |
1060 | * Returns: %TRUE if the resource was found, %FALSE otherwise |
1061 | */ |
1062 | gboolean |
1063 | gtk_recent_manager_has_item (GtkRecentManager *manager, |
1064 | const char *uri) |
1065 | { |
1066 | GtkRecentManagerPrivate *priv; |
1067 | |
1068 | g_return_val_if_fail (GTK_IS_RECENT_MANAGER (manager), FALSE); |
1069 | g_return_val_if_fail (uri != NULL, FALSE); |
1070 | |
1071 | priv = manager->priv; |
1072 | g_return_val_if_fail (priv->recent_items != NULL, FALSE); |
1073 | |
1074 | return g_bookmark_file_has_item (bookmark: priv->recent_items, uri); |
1075 | } |
1076 | |
1077 | static void |
1078 | build_recent_info (GBookmarkFile *bookmarks, |
1079 | GtkRecentInfo *info) |
1080 | { |
1081 | char **apps, **groups; |
1082 | gsize apps_len, groups_len, i; |
1083 | int app_index; |
1084 | |
1085 | g_assert (bookmarks != NULL); |
1086 | g_assert (info != NULL); |
1087 | |
1088 | info->display_name = g_bookmark_file_get_title (bookmark: bookmarks, uri: info->uri, NULL); |
1089 | info->description = g_bookmark_file_get_description (bookmark: bookmarks, uri: info->uri, NULL); |
1090 | info->mime_type = g_bookmark_file_get_mime_type (bookmark: bookmarks, uri: info->uri, NULL); |
1091 | |
1092 | info->is_private = g_bookmark_file_get_is_private (bookmark: bookmarks, uri: info->uri, NULL); |
1093 | |
1094 | info->added = g_bookmark_file_get_added_date_time (bookmark: bookmarks, uri: info->uri, NULL); |
1095 | info->modified = g_bookmark_file_get_modified_date_time (bookmark: bookmarks, uri: info->uri, NULL); |
1096 | info->visited = g_bookmark_file_get_visited_date_time (bookmark: bookmarks, uri: info->uri, NULL); |
1097 | |
1098 | groups = g_bookmark_file_get_groups (bookmark: bookmarks, uri: info->uri, length: &groups_len, NULL); |
1099 | info->groups = g_malloc (n_bytes: sizeof (char *) * groups_len); |
1100 | info->n_groups = groups_len; |
1101 | for (i = 0; i < groups_len; i++) |
1102 | info->groups[i] = g_strdup (str: groups[i]); |
1103 | |
1104 | g_strfreev (str_array: groups); |
1105 | |
1106 | app_index = 0; |
1107 | apps = g_bookmark_file_get_applications (bookmark: bookmarks, uri: info->uri, length: &apps_len, NULL); |
1108 | info->applications = g_malloc (n_bytes: sizeof (RecentAppInfo ) * apps_len); |
1109 | info->n_applications = 0; |
1110 | for (i = 0; i < apps_len; i++) |
1111 | { |
1112 | char *app_name, *app_exec; |
1113 | guint count; |
1114 | GDateTime *stamp; |
1115 | RecentAppInfo *app_info; |
1116 | gboolean res; |
1117 | |
1118 | app_name = apps[i]; |
1119 | |
1120 | res = g_bookmark_file_get_application_info (bookmark: bookmarks, uri: info->uri, name: app_name, |
1121 | exec: &app_exec, |
1122 | count: &count, |
1123 | stamp: &stamp, |
1124 | NULL); |
1125 | if (!res) |
1126 | continue; |
1127 | |
1128 | app_info = &info->applications[app_index]; |
1129 | app_info->name= g_strdup (str: app_name); |
1130 | app_info->exec = app_exec; |
1131 | app_info->count = count; |
1132 | app_info->stamp = g_date_time_ref (datetime: stamp); |
1133 | |
1134 | g_hash_table_replace (hash_table: info->apps_lookup, key: app_info->name, value: app_info); |
1135 | |
1136 | app_index ++; |
1137 | info->n_applications ++; |
1138 | } |
1139 | |
1140 | g_strfreev (str_array: apps); |
1141 | } |
1142 | |
1143 | /** |
1144 | * gtk_recent_manager_lookup_item: |
1145 | * @manager: a `GtkRecentManager` |
1146 | * @uri: a URI |
1147 | * @error: (nullable): a return location for a `GError` |
1148 | * |
1149 | * Searches for a URI inside the recently used resources list, and |
1150 | * returns a `GtkRecentInfo` containing information about the resource |
1151 | * like its MIME type, or its display name. |
1152 | * |
1153 | * Returns: (nullable): a `GtkRecentInfo` containing information |
1154 | * about the resource pointed by @uri, or %NULL if the URI was |
1155 | * not registered in the recently used resources list. Free with |
1156 | * [method@Gtk.RecentInfo.unref]. |
1157 | */ |
1158 | GtkRecentInfo * |
1159 | gtk_recent_manager_lookup_item (GtkRecentManager *manager, |
1160 | const char *uri, |
1161 | GError **error) |
1162 | { |
1163 | GtkRecentManagerPrivate *priv; |
1164 | GtkRecentInfo *info = NULL; |
1165 | |
1166 | g_return_val_if_fail (GTK_IS_RECENT_MANAGER (manager), NULL); |
1167 | g_return_val_if_fail (uri != NULL, NULL); |
1168 | g_return_val_if_fail (error == NULL || *error == NULL, NULL); |
1169 | |
1170 | priv = manager->priv; |
1171 | if (!priv->recent_items) |
1172 | { |
1173 | priv->recent_items = g_bookmark_file_new (); |
1174 | priv->size = 0; |
1175 | |
1176 | g_set_error (err: error, GTK_RECENT_MANAGER_ERROR, |
1177 | code: GTK_RECENT_MANAGER_ERROR_NOT_FOUND, |
1178 | _("Unable to find an item with URI “%s”" ), |
1179 | uri); |
1180 | |
1181 | return NULL; |
1182 | } |
1183 | |
1184 | if (!g_bookmark_file_has_item (bookmark: priv->recent_items, uri)) |
1185 | { |
1186 | g_set_error (err: error, GTK_RECENT_MANAGER_ERROR, |
1187 | code: GTK_RECENT_MANAGER_ERROR_NOT_FOUND, |
1188 | _("Unable to find an item with URI “%s”" ), |
1189 | uri); |
1190 | return NULL; |
1191 | } |
1192 | |
1193 | info = gtk_recent_info_new (uri); |
1194 | g_return_val_if_fail (info != NULL, NULL); |
1195 | |
1196 | /* fill the RecentInfo structure with the data retrieved by our |
1197 | * parser object from the storage file |
1198 | */ |
1199 | build_recent_info (bookmarks: priv->recent_items, info); |
1200 | |
1201 | return info; |
1202 | } |
1203 | |
1204 | /** |
1205 | * gtk_recent_manager_move_item: |
1206 | * @manager: a `GtkRecentManager` |
1207 | * @uri: the URI of a recently used resource |
1208 | * @new_uri: (nullable): the new URI of the recently used resource, or |
1209 | * %NULL to remove the item pointed by @uri in the list |
1210 | * @error: (nullable): a return location for a `GError` |
1211 | * |
1212 | * Changes the location of a recently used resource from @uri to @new_uri. |
1213 | * |
1214 | * Please note that this function will not affect the resource pointed |
1215 | * by the URIs, but only the URI used in the recently used resources list. |
1216 | * |
1217 | * Returns: %TRUE on success |
1218 | */ |
1219 | gboolean |
1220 | gtk_recent_manager_move_item (GtkRecentManager *recent_manager, |
1221 | const char *uri, |
1222 | const char *new_uri, |
1223 | GError **error) |
1224 | { |
1225 | GtkRecentManagerPrivate *priv; |
1226 | GError *move_error; |
1227 | |
1228 | g_return_val_if_fail (GTK_IS_RECENT_MANAGER (recent_manager), FALSE); |
1229 | g_return_val_if_fail (uri != NULL, FALSE); |
1230 | g_return_val_if_fail (error == NULL || *error == NULL, FALSE); |
1231 | |
1232 | priv = recent_manager->priv; |
1233 | |
1234 | if (!priv->recent_items) |
1235 | { |
1236 | g_set_error (err: error, GTK_RECENT_MANAGER_ERROR, |
1237 | code: GTK_RECENT_MANAGER_ERROR_NOT_FOUND, |
1238 | _("Unable to find an item with URI “%s”" ), |
1239 | uri); |
1240 | return FALSE; |
1241 | } |
1242 | |
1243 | if (!g_bookmark_file_has_item (bookmark: priv->recent_items, uri)) |
1244 | { |
1245 | g_set_error (err: error, GTK_RECENT_MANAGER_ERROR, |
1246 | code: GTK_RECENT_MANAGER_ERROR_NOT_FOUND, |
1247 | _("Unable to find an item with URI “%s”" ), |
1248 | uri); |
1249 | return FALSE; |
1250 | } |
1251 | |
1252 | move_error = NULL; |
1253 | if (!g_bookmark_file_move_item (bookmark: priv->recent_items, |
1254 | old_uri: uri, |
1255 | new_uri, |
1256 | error: &move_error)) |
1257 | { |
1258 | g_error_free (error: move_error); |
1259 | |
1260 | g_set_error (err: error, GTK_RECENT_MANAGER_ERROR, |
1261 | code: GTK_RECENT_MANAGER_ERROR_UNKNOWN, |
1262 | _("Unable to move the item with URI “%s” to “%s”" ), |
1263 | uri, new_uri); |
1264 | return FALSE; |
1265 | } |
1266 | |
1267 | priv->is_dirty = TRUE; |
1268 | gtk_recent_manager_changed (manager: recent_manager); |
1269 | |
1270 | return TRUE; |
1271 | } |
1272 | |
1273 | /** |
1274 | * gtk_recent_manager_get_items: |
1275 | * @manager: a `GtkRecentManager` |
1276 | * |
1277 | * Gets the list of recently used resources. |
1278 | * |
1279 | * Returns: (element-type GtkRecentInfo) (transfer full): a list of |
1280 | * newly allocated `GtkRecentInfo objects`. Use |
1281 | * [method@Gtk.RecentInfo.unref] on each item inside the list, and then |
1282 | * free the list itself using g_list_free(). |
1283 | */ |
1284 | GList * |
1285 | gtk_recent_manager_get_items (GtkRecentManager *manager) |
1286 | { |
1287 | GtkRecentManagerPrivate *priv; |
1288 | GList *retval = NULL; |
1289 | char **uris; |
1290 | gsize uris_len, i; |
1291 | |
1292 | g_return_val_if_fail (GTK_IS_RECENT_MANAGER (manager), NULL); |
1293 | |
1294 | priv = manager->priv; |
1295 | if (!priv->recent_items) |
1296 | return NULL; |
1297 | |
1298 | uris = g_bookmark_file_get_uris (bookmark: priv->recent_items, length: &uris_len); |
1299 | for (i = 0; i < uris_len; i++) |
1300 | { |
1301 | GtkRecentInfo *info; |
1302 | |
1303 | info = gtk_recent_info_new (uri: uris[i]); |
1304 | build_recent_info (bookmarks: priv->recent_items, info); |
1305 | |
1306 | retval = g_list_prepend (list: retval, data: info); |
1307 | } |
1308 | |
1309 | g_strfreev (str_array: uris); |
1310 | |
1311 | return retval; |
1312 | } |
1313 | |
1314 | static void |
1315 | purge_recent_items_list (GtkRecentManager *manager, |
1316 | GError **error) |
1317 | { |
1318 | GtkRecentManagerPrivate *priv = manager->priv; |
1319 | |
1320 | if (priv->recent_items == NULL) |
1321 | return; |
1322 | |
1323 | g_bookmark_file_free (bookmark: priv->recent_items); |
1324 | priv->recent_items = g_bookmark_file_new (); |
1325 | priv->size = 0; |
1326 | |
1327 | /* emit the changed signal, to ensure that the purge is written */ |
1328 | priv->is_dirty = TRUE; |
1329 | gtk_recent_manager_changed (manager); |
1330 | } |
1331 | |
1332 | /** |
1333 | * gtk_recent_manager_purge_items: |
1334 | * @manager: a `GtkRecentManager` |
1335 | * @error: (nullable): a return location for a `GError` |
1336 | * |
1337 | * Purges every item from the recently used resources list. |
1338 | * |
1339 | * Returns: the number of items that have been removed from the |
1340 | * recently used resources list |
1341 | */ |
1342 | int |
1343 | gtk_recent_manager_purge_items (GtkRecentManager *manager, |
1344 | GError **error) |
1345 | { |
1346 | GtkRecentManagerPrivate *priv; |
1347 | int count, purged; |
1348 | |
1349 | g_return_val_if_fail (GTK_IS_RECENT_MANAGER (manager), -1); |
1350 | |
1351 | priv = manager->priv; |
1352 | if (!priv->recent_items) |
1353 | return 0; |
1354 | |
1355 | count = g_bookmark_file_get_size (bookmark: priv->recent_items); |
1356 | if (!count) |
1357 | return 0; |
1358 | |
1359 | purge_recent_items_list (manager, error); |
1360 | |
1361 | purged = count - g_bookmark_file_get_size (bookmark: priv->recent_items); |
1362 | |
1363 | return purged; |
1364 | } |
1365 | |
1366 | static gboolean |
1367 | emit_manager_changed (gpointer data) |
1368 | { |
1369 | GtkRecentManager *manager = data; |
1370 | |
1371 | manager->priv->changed_age = 0; |
1372 | manager->priv->changed_timeout = 0; |
1373 | |
1374 | g_signal_emit (instance: manager, signal_id: signal_changed, detail: 0); |
1375 | |
1376 | return FALSE; |
1377 | } |
1378 | |
1379 | static void |
1380 | gtk_recent_manager_changed (GtkRecentManager *manager) |
1381 | { |
1382 | /* coalesce consecutive changes |
1383 | * |
1384 | * we schedule a write in 250 msecs immediately; if we get more than one |
1385 | * request per millisecond before the timeout has a chance to run, we |
1386 | * schedule an emission immediately. |
1387 | */ |
1388 | if (manager->priv->changed_timeout == 0) |
1389 | { |
1390 | manager->priv->changed_timeout = g_timeout_add (interval: 250, function: emit_manager_changed, data: manager); |
1391 | gdk_source_set_static_name_by_id (tag: manager->priv->changed_timeout, name: "[gtk] emit_manager_changed" ); |
1392 | } |
1393 | else |
1394 | { |
1395 | manager->priv->changed_age += 1; |
1396 | |
1397 | if (manager->priv->changed_age > 250) |
1398 | { |
1399 | g_source_remove (tag: manager->priv->changed_timeout); |
1400 | g_signal_emit (instance: manager, signal_id: signal_changed, detail: 0); |
1401 | |
1402 | manager->priv->changed_age = 0; |
1403 | manager->priv->changed_timeout = 0; |
1404 | } |
1405 | } |
1406 | } |
1407 | |
1408 | static void |
1409 | gtk_recent_manager_clamp_to_age (GtkRecentManager *manager, |
1410 | int age) |
1411 | { |
1412 | GtkRecentManagerPrivate *priv = manager->priv; |
1413 | char **uris; |
1414 | gsize n_uris, i; |
1415 | GDateTime *now; |
1416 | |
1417 | if (G_UNLIKELY (!priv->recent_items)) |
1418 | return; |
1419 | |
1420 | now = g_date_time_new_now_utc (); |
1421 | |
1422 | uris = g_bookmark_file_get_uris (bookmark: priv->recent_items, length: &n_uris); |
1423 | |
1424 | for (i = 0; i < n_uris; i++) |
1425 | { |
1426 | const char *uri = uris[i]; |
1427 | GDateTime *modified; |
1428 | int item_age; |
1429 | |
1430 | modified = g_bookmark_file_get_modified_date_time (bookmark: priv->recent_items, uri, NULL); |
1431 | |
1432 | item_age = (int) (g_date_time_difference (end: now, begin: modified) / (double)G_TIME_SPAN_DAY); |
1433 | if (item_age > age) |
1434 | g_bookmark_file_remove_item (bookmark: priv->recent_items, uri, NULL); |
1435 | } |
1436 | |
1437 | g_strfreev (str_array: uris); |
1438 | } |
1439 | |
1440 | static void |
1441 | gtk_recent_manager_clamp_to_size (GtkRecentManager *manager, |
1442 | const int size) |
1443 | { |
1444 | GtkRecentManagerPrivate *priv = manager->priv; |
1445 | char **uris; |
1446 | gsize n_uris, i; |
1447 | |
1448 | if (G_UNLIKELY (!priv->recent_items) || G_UNLIKELY (size < 0)) |
1449 | return; |
1450 | |
1451 | uris = g_bookmark_file_get_uris (bookmark: priv->recent_items, length: &n_uris); |
1452 | |
1453 | if (n_uris < size) |
1454 | { |
1455 | g_strfreev (str_array: uris); |
1456 | return; |
1457 | } |
1458 | |
1459 | for (i = 0; i < n_uris - size; i++) |
1460 | { |
1461 | const char *uri = uris[i]; |
1462 | g_bookmark_file_remove_item (bookmark: priv->recent_items, uri, NULL); |
1463 | } |
1464 | |
1465 | g_strfreev (str_array: uris); |
1466 | } |
1467 | |
1468 | /***************** |
1469 | * GtkRecentInfo * |
1470 | *****************/ |
1471 | |
1472 | G_DEFINE_BOXED_TYPE (GtkRecentInfo, gtk_recent_info, |
1473 | gtk_recent_info_ref, |
1474 | gtk_recent_info_unref) |
1475 | |
1476 | static GtkRecentInfo * |
1477 | gtk_recent_info_new (const char *uri) |
1478 | { |
1479 | GtkRecentInfo *info; |
1480 | |
1481 | g_assert (uri != NULL); |
1482 | |
1483 | info = g_new0 (GtkRecentInfo, 1); |
1484 | info->uri = g_strdup (str: uri); |
1485 | |
1486 | info->applications = NULL; |
1487 | info->apps_lookup = g_hash_table_new (hash_func: g_str_hash, key_equal_func: g_str_equal); |
1488 | |
1489 | info->groups = NULL; |
1490 | |
1491 | info->ref_count = 1; |
1492 | |
1493 | return info; |
1494 | } |
1495 | |
1496 | static void |
1497 | gtk_recent_info_free (GtkRecentInfo *recent_info) |
1498 | { |
1499 | int i; |
1500 | |
1501 | if (!recent_info) |
1502 | return; |
1503 | |
1504 | g_free (mem: recent_info->uri); |
1505 | g_free (mem: recent_info->display_name); |
1506 | g_free (mem: recent_info->description); |
1507 | g_free (mem: recent_info->mime_type); |
1508 | |
1509 | for (i = 0; i < recent_info->n_applications; i ++) |
1510 | { |
1511 | const RecentAppInfo *app_info = &recent_info->applications[i]; |
1512 | |
1513 | g_free (mem: app_info->name); |
1514 | g_free (mem: app_info->exec); |
1515 | g_date_time_unref (datetime: app_info->stamp); |
1516 | } |
1517 | g_free (mem: recent_info->applications); |
1518 | |
1519 | if (recent_info->apps_lookup) |
1520 | g_hash_table_destroy (hash_table: recent_info->apps_lookup); |
1521 | |
1522 | for (i = 0; i < recent_info->n_groups; i ++) |
1523 | g_free (mem: recent_info->groups[i]); |
1524 | |
1525 | g_free (mem: recent_info->groups); |
1526 | |
1527 | g_free (mem: recent_info); |
1528 | } |
1529 | |
1530 | /** |
1531 | * gtk_recent_info_ref: |
1532 | * @info: a `GtkRecentInfo` |
1533 | * |
1534 | * Increases the reference count of @recent_info by one. |
1535 | * |
1536 | * Returns: the recent info object with its reference count |
1537 | * increased by one |
1538 | */ |
1539 | GtkRecentInfo * |
1540 | gtk_recent_info_ref (GtkRecentInfo *info) |
1541 | { |
1542 | g_return_val_if_fail (info != NULL, NULL); |
1543 | g_return_val_if_fail (info->ref_count > 0, NULL); |
1544 | |
1545 | info->ref_count += 1; |
1546 | |
1547 | return info; |
1548 | } |
1549 | |
1550 | /** |
1551 | * gtk_recent_info_unref: |
1552 | * @info: a `GtkRecentInfo` |
1553 | * |
1554 | * Decreases the reference count of @info by one. |
1555 | * |
1556 | * If the reference count reaches zero, @info is |
1557 | * deallocated, and the memory freed. |
1558 | */ |
1559 | void |
1560 | gtk_recent_info_unref (GtkRecentInfo *info) |
1561 | { |
1562 | g_return_if_fail (info != NULL); |
1563 | g_return_if_fail (info->ref_count > 0); |
1564 | |
1565 | info->ref_count -= 1; |
1566 | |
1567 | if (info->ref_count == 0) |
1568 | gtk_recent_info_free (recent_info: info); |
1569 | } |
1570 | |
1571 | /** |
1572 | * gtk_recent_info_get_uri: |
1573 | * @info: a `tkRecentInfo` |
1574 | * |
1575 | * Gets the URI of the resource. |
1576 | * |
1577 | * Returns: the URI of the resource. The returned string is |
1578 | * owned by the recent manager, and should not be freed. |
1579 | */ |
1580 | const char * |
1581 | gtk_recent_info_get_uri (GtkRecentInfo *info) |
1582 | { |
1583 | g_return_val_if_fail (info != NULL, NULL); |
1584 | |
1585 | return info->uri; |
1586 | } |
1587 | |
1588 | /** |
1589 | * gtk_recent_info_get_display_name: |
1590 | * @info: a `GtkRecentInfo` |
1591 | * |
1592 | * Gets the name of the resource. |
1593 | * |
1594 | * If none has been defined, the basename |
1595 | * of the resource is obtained. |
1596 | * |
1597 | * Returns: the display name of the resource. The returned string |
1598 | * is owned by the recent manager, and should not be freed. |
1599 | */ |
1600 | const char * |
1601 | gtk_recent_info_get_display_name (GtkRecentInfo *info) |
1602 | { |
1603 | g_return_val_if_fail (info != NULL, NULL); |
1604 | |
1605 | if (!info->display_name) |
1606 | info->display_name = gtk_recent_info_get_short_name (info); |
1607 | |
1608 | return info->display_name; |
1609 | } |
1610 | |
1611 | /** |
1612 | * gtk_recent_info_get_description: |
1613 | * @info: a `GtkRecentInfo` |
1614 | * |
1615 | * Gets the (short) description of the resource. |
1616 | * |
1617 | * Returns: the description of the resource. The returned string |
1618 | * is owned by the recent manager, and should not be freed. |
1619 | **/ |
1620 | const char * |
1621 | gtk_recent_info_get_description (GtkRecentInfo *info) |
1622 | { |
1623 | g_return_val_if_fail (info != NULL, NULL); |
1624 | |
1625 | return info->description; |
1626 | } |
1627 | |
1628 | /** |
1629 | * gtk_recent_info_get_mime_type: |
1630 | * @info: a `GtkRecentInfo` |
1631 | * |
1632 | * Gets the MIME type of the resource. |
1633 | * |
1634 | * Returns: the MIME type of the resource. The returned string |
1635 | * is owned by the recent manager, and should not be freed. |
1636 | */ |
1637 | const char * |
1638 | gtk_recent_info_get_mime_type (GtkRecentInfo *info) |
1639 | { |
1640 | g_return_val_if_fail (info != NULL, NULL); |
1641 | |
1642 | if (!info->mime_type) |
1643 | info->mime_type = g_strdup (GTK_RECENT_DEFAULT_MIME); |
1644 | |
1645 | return info->mime_type; |
1646 | } |
1647 | |
1648 | /** |
1649 | * gtk_recent_info_get_added: |
1650 | * @info: a `GtkRecentInfo` |
1651 | * |
1652 | * Gets the time when the resource |
1653 | * was added to the recently used resources list. |
1654 | * |
1655 | * Returns: (transfer none): a `GDateTime` for the time |
1656 | * when the resource was added |
1657 | */ |
1658 | GDateTime * |
1659 | gtk_recent_info_get_added (GtkRecentInfo *info) |
1660 | { |
1661 | g_return_val_if_fail (info != NULL, NULL); |
1662 | |
1663 | return info->added; |
1664 | } |
1665 | |
1666 | /** |
1667 | * gtk_recent_info_get_modified: |
1668 | * @info: a `GtkRecentInfo` |
1669 | * |
1670 | * Gets the time when the meta-data |
1671 | * for the resource was last modified. |
1672 | * |
1673 | * Returns: (transfer none): a `GDateTime` for the time |
1674 | * when the resource was last modified |
1675 | */ |
1676 | GDateTime * |
1677 | gtk_recent_info_get_modified (GtkRecentInfo *info) |
1678 | { |
1679 | g_return_val_if_fail (info != NULL, NULL); |
1680 | |
1681 | return info->modified; |
1682 | } |
1683 | |
1684 | /** |
1685 | * gtk_recent_info_get_visited: |
1686 | * @info: a `GtkRecentInfo` |
1687 | * |
1688 | * Gets the time when the meta-data |
1689 | * for the resource was last visited. |
1690 | * |
1691 | * Returns: (transfer none): a `GDateTime` for the time |
1692 | * when the resource was last visited |
1693 | */ |
1694 | GDateTime * |
1695 | gtk_recent_info_get_visited (GtkRecentInfo *info) |
1696 | { |
1697 | g_return_val_if_fail (info != NULL, NULL); |
1698 | |
1699 | return info->visited; |
1700 | } |
1701 | |
1702 | /** |
1703 | * gtk_recent_info_get_private_hint: |
1704 | * @info: a `GtkRecentInfo` |
1705 | * |
1706 | * Gets the value of the “private” flag. |
1707 | * |
1708 | * Resources in the recently used list that have this flag |
1709 | * set to %TRUE should only be displayed by the applications |
1710 | * that have registered them. |
1711 | * |
1712 | * Returns: %TRUE if the private flag was found, %FALSE otherwise |
1713 | */ |
1714 | gboolean |
1715 | gtk_recent_info_get_private_hint (GtkRecentInfo *info) |
1716 | { |
1717 | g_return_val_if_fail (info != NULL, FALSE); |
1718 | |
1719 | return info->is_private; |
1720 | } |
1721 | |
1722 | /** |
1723 | * gtk_recent_info_get_application_info: |
1724 | * @info: a `GtkRecentInfo` |
1725 | * @app_name: the name of the application that has registered this item |
1726 | * @app_exec: (transfer none) (out): return location for the string containing |
1727 | * the command line |
1728 | * @count: (out): return location for the number of times this item was registered |
1729 | * @stamp: (out) (transfer none): return location for the time this item was last |
1730 | * registered for this application |
1731 | * |
1732 | * Gets the data regarding the application that has registered the resource |
1733 | * pointed by @info. |
1734 | * |
1735 | * If the command line contains any escape characters defined inside the |
1736 | * storage specification, they will be expanded. |
1737 | * |
1738 | * Returns: %TRUE if an application with @app_name has registered this |
1739 | * resource inside the recently used list, or %FALSE otherwise. The |
1740 | * @app_exec string is owned by the `GtkRecentInfo` and should not be |
1741 | * modified or freed |
1742 | */ |
1743 | gboolean |
1744 | gtk_recent_info_get_application_info (GtkRecentInfo *info, |
1745 | const char *app_name, |
1746 | const char **app_exec, |
1747 | guint *count, |
1748 | GDateTime **stamp) |
1749 | { |
1750 | RecentAppInfo *ai; |
1751 | |
1752 | g_return_val_if_fail (info != NULL, FALSE); |
1753 | g_return_val_if_fail (app_name != NULL, FALSE); |
1754 | |
1755 | ai = (RecentAppInfo *) g_hash_table_lookup (hash_table: info->apps_lookup, |
1756 | key: app_name); |
1757 | if (!ai) |
1758 | { |
1759 | g_warning ("No registered application with name '%s' " |
1760 | "for item with URI '%s' found" , |
1761 | app_name, |
1762 | info->uri); |
1763 | return FALSE; |
1764 | } |
1765 | |
1766 | if (app_exec) |
1767 | *app_exec = ai->exec; |
1768 | |
1769 | if (count) |
1770 | *count = ai->count; |
1771 | |
1772 | if (stamp) |
1773 | *stamp = ai->stamp; |
1774 | |
1775 | return TRUE; |
1776 | } |
1777 | |
1778 | /** |
1779 | * gtk_recent_info_get_applications: |
1780 | * @info: a `GtkRecentInfo` |
1781 | * @length: (out) (optional): return location for the length of the returned list |
1782 | * |
1783 | * Retrieves the list of applications that have registered this resource. |
1784 | * |
1785 | * Returns: (array length=length zero-terminated=1) (transfer full): a newly |
1786 | * allocated %NULL-terminated array of strings. Use g_strfreev() to free it. |
1787 | */ |
1788 | char ** |
1789 | gtk_recent_info_get_applications (GtkRecentInfo *info, |
1790 | gsize *length) |
1791 | { |
1792 | char **retval; |
1793 | gsize n_apps, i; |
1794 | |
1795 | g_return_val_if_fail (info != NULL, NULL); |
1796 | |
1797 | if (!info->applications) |
1798 | { |
1799 | if (length) |
1800 | *length = 0; |
1801 | |
1802 | return NULL; |
1803 | } |
1804 | |
1805 | n_apps = info->n_applications; |
1806 | |
1807 | retval = g_new0 (char *, n_apps + 1); |
1808 | |
1809 | for (i = 0; i < info->n_applications; i ++) |
1810 | { |
1811 | const RecentAppInfo *ai = &info->applications[i]; |
1812 | |
1813 | retval[i] = g_strdup (str: ai->name); |
1814 | } |
1815 | retval[i] = NULL; |
1816 | |
1817 | if (length) |
1818 | *length = info->n_applications; |
1819 | |
1820 | return retval; |
1821 | } |
1822 | |
1823 | /** |
1824 | * gtk_recent_info_has_application: |
1825 | * @info: a `GtkRecentInfo` |
1826 | * @app_name: a string containing an application name |
1827 | * |
1828 | * Checks whether an application registered this resource using @app_name. |
1829 | * |
1830 | * Returns: %TRUE if an application with name @app_name was found, |
1831 | * %FALSE otherwise |
1832 | */ |
1833 | gboolean |
1834 | gtk_recent_info_has_application (GtkRecentInfo *info, |
1835 | const char *app_name) |
1836 | { |
1837 | g_return_val_if_fail (info != NULL, FALSE); |
1838 | g_return_val_if_fail (app_name != NULL, FALSE); |
1839 | |
1840 | return (NULL != g_hash_table_lookup (hash_table: info->apps_lookup, key: app_name)); |
1841 | } |
1842 | |
1843 | /** |
1844 | * gtk_recent_info_last_application: |
1845 | * @info: a `GtkRecentInfo` |
1846 | * |
1847 | * Gets the name of the last application that have registered the |
1848 | * recently used resource represented by @info. |
1849 | * |
1850 | * Returns: an application name. Use g_free() to free it. |
1851 | */ |
1852 | char * |
1853 | gtk_recent_info_last_application (GtkRecentInfo *info) |
1854 | { |
1855 | int i; |
1856 | GDateTime *last_stamp = NULL; |
1857 | char *name = NULL; |
1858 | |
1859 | g_return_val_if_fail (info != NULL, NULL); |
1860 | |
1861 | for (i = 0; i < info->n_applications; i ++) |
1862 | { |
1863 | const RecentAppInfo *ai = &info->applications[i]; |
1864 | |
1865 | if (last_stamp == NULL || |
1866 | g_date_time_compare (dt1: ai->stamp, dt2: last_stamp) > 0) |
1867 | { |
1868 | name = ai->name; |
1869 | last_stamp = ai->stamp; |
1870 | } |
1871 | } |
1872 | |
1873 | return g_strdup (str: name); |
1874 | } |
1875 | |
1876 | /** |
1877 | * gtk_recent_info_get_gicon: |
1878 | * @info: a `GtkRecentInfo` |
1879 | * |
1880 | * Retrieves the icon associated to the resource MIME type. |
1881 | * |
1882 | * Returns: (nullable) (transfer full): a `GIcon` containing the icon |
1883 | */ |
1884 | GIcon * |
1885 | gtk_recent_info_get_gicon (GtkRecentInfo *info) |
1886 | { |
1887 | GIcon *icon = NULL; |
1888 | char *content_type; |
1889 | |
1890 | g_return_val_if_fail (info != NULL, NULL); |
1891 | |
1892 | if (info->mime_type != NULL && |
1893 | (content_type = g_content_type_from_mime_type (mime_type: info->mime_type)) != NULL) |
1894 | { |
1895 | icon = g_content_type_get_icon (type: content_type); |
1896 | g_free (mem: content_type); |
1897 | } |
1898 | else |
1899 | { |
1900 | if (info->mime_type && |
1901 | strcmp (s1: info->mime_type, s2: "x-directory/normal" ) == 0) |
1902 | icon = g_themed_icon_new (iconname: "folder" ); |
1903 | else |
1904 | icon = g_themed_icon_new (iconname: "text-x-generic" ); |
1905 | } |
1906 | |
1907 | return icon; |
1908 | } |
1909 | |
1910 | /** |
1911 | * gtk_recent_info_is_local: |
1912 | * @info: a `GtkRecentInfo` |
1913 | * |
1914 | * Checks whether the resource is local or not by looking at the |
1915 | * scheme of its URI. |
1916 | * |
1917 | * Returns: %TRUE if the resource is local |
1918 | */ |
1919 | gboolean |
1920 | gtk_recent_info_is_local (GtkRecentInfo *info) |
1921 | { |
1922 | g_return_val_if_fail (info != NULL, FALSE); |
1923 | |
1924 | return has_case_prefix (haystack: info->uri, needle: "file:/" ); |
1925 | } |
1926 | |
1927 | /** |
1928 | * gtk_recent_info_exists: |
1929 | * @info: a `GtkRecentInfo` |
1930 | * |
1931 | * Checks whether the resource pointed by @info still exists. |
1932 | * At the moment this check is done only on resources pointing |
1933 | * to local files. |
1934 | * |
1935 | * Returns: %TRUE if the resource exists |
1936 | */ |
1937 | gboolean |
1938 | gtk_recent_info_exists (GtkRecentInfo *info) |
1939 | { |
1940 | char *filename; |
1941 | GStatBuf stat_buf; |
1942 | gboolean retval = FALSE; |
1943 | |
1944 | g_return_val_if_fail (info != NULL, FALSE); |
1945 | |
1946 | /* we guarantee only local resources */ |
1947 | if (!gtk_recent_info_is_local (info)) |
1948 | return FALSE; |
1949 | |
1950 | filename = g_filename_from_uri (uri: info->uri, NULL, NULL); |
1951 | if (filename) |
1952 | { |
1953 | if (g_stat (file: filename, buf: &stat_buf) == 0) |
1954 | retval = TRUE; |
1955 | |
1956 | g_free (mem: filename); |
1957 | } |
1958 | |
1959 | return retval; |
1960 | } |
1961 | |
1962 | /** |
1963 | * gtk_recent_info_match: |
1964 | * @info_a: a `GtkRecentInfo` |
1965 | * @info_b: a `GtkRecentInfo` |
1966 | * |
1967 | * Checks whether two `GtkRecentInfo` point to the same resource. |
1968 | * |
1969 | * Returns: %TRUE if both `GtkRecentInfo` point to the same |
1970 | * resource, %FALSE otherwise |
1971 | */ |
1972 | gboolean |
1973 | gtk_recent_info_match (GtkRecentInfo *info_a, |
1974 | GtkRecentInfo *info_b) |
1975 | { |
1976 | g_return_val_if_fail (info_a != NULL, FALSE); |
1977 | g_return_val_if_fail (info_b != NULL, FALSE); |
1978 | |
1979 | return (0 == strcmp (s1: info_a->uri, s2: info_b->uri)); |
1980 | } |
1981 | |
1982 | /* taken from gnome-vfs-uri.c */ |
1983 | static const char * |
1984 | get_method_string (const char *substring, |
1985 | char **method_string) |
1986 | { |
1987 | const char *p; |
1988 | char *method; |
1989 | |
1990 | for (p = substring; |
1991 | g_ascii_isalnum (*p) || *p == '+' || *p == '-' || *p == '.'; |
1992 | p++) |
1993 | ; |
1994 | |
1995 | if (*p == ':' |
1996 | #ifdef G_OS_WIN32 |
1997 | && |
1998 | !(p == substring + 1 && g_ascii_isalpha (*substring)) |
1999 | #endif |
2000 | ) |
2001 | { |
2002 | /* Found toplevel method specification. */ |
2003 | method = g_strndup (str: substring, n: p - substring); |
2004 | *method_string = g_ascii_strdown (str: method, len: -1); |
2005 | g_free (mem: method); |
2006 | p++; |
2007 | } |
2008 | else |
2009 | { |
2010 | *method_string = g_strdup (str: "file" ); |
2011 | p = substring; |
2012 | } |
2013 | |
2014 | return p; |
2015 | } |
2016 | |
2017 | /* Stolen from gnome_vfs_make_valid_utf8() */ |
2018 | static char * |
2019 | make_valid_utf8 (const char *name) |
2020 | { |
2021 | GString *string; |
2022 | const char *remainder, *invalid; |
2023 | int remaining_bytes, valid_bytes; |
2024 | |
2025 | string = NULL; |
2026 | remainder = name; |
2027 | remaining_bytes = name ? strlen (s: name) : 0; |
2028 | |
2029 | while (remaining_bytes != 0) |
2030 | { |
2031 | if (g_utf8_validate (str: remainder, max_len: remaining_bytes, end: &invalid)) |
2032 | break; |
2033 | |
2034 | valid_bytes = invalid - remainder; |
2035 | |
2036 | if (string == NULL) |
2037 | string = g_string_sized_new (dfl_size: remaining_bytes); |
2038 | |
2039 | g_string_append_len (string, val: remainder, len: valid_bytes); |
2040 | g_string_append_c (string, '?'); |
2041 | |
2042 | remaining_bytes -= valid_bytes + 1; |
2043 | remainder = invalid + 1; |
2044 | } |
2045 | |
2046 | if (string == NULL) |
2047 | return g_strdup (str: name); |
2048 | |
2049 | g_string_append (string, val: remainder); |
2050 | g_assert (g_utf8_validate (string->str, -1, NULL)); |
2051 | |
2052 | return g_string_free (string, FALSE); |
2053 | } |
2054 | |
2055 | static char * |
2056 | get_uri_shortname_for_display (const char *uri) |
2057 | { |
2058 | char *name = NULL; |
2059 | gboolean validated = FALSE; |
2060 | |
2061 | if (has_case_prefix (haystack: uri, needle: "file:/" )) |
2062 | { |
2063 | char *local_file; |
2064 | |
2065 | local_file = g_filename_from_uri (uri, NULL, NULL); |
2066 | |
2067 | if (local_file) |
2068 | { |
2069 | name = g_filename_display_basename (filename: local_file); |
2070 | validated = TRUE; |
2071 | } |
2072 | |
2073 | g_free (mem: local_file); |
2074 | } |
2075 | |
2076 | if (!name) |
2077 | { |
2078 | char *method; |
2079 | char *local_file; |
2080 | const char *rest; |
2081 | |
2082 | rest = get_method_string (substring: uri, method_string: &method); |
2083 | local_file = g_filename_display_basename (filename: rest); |
2084 | |
2085 | name = g_strconcat (string1: method, ": " , local_file, NULL); |
2086 | |
2087 | g_free (mem: local_file); |
2088 | g_free (mem: method); |
2089 | } |
2090 | |
2091 | g_assert (name != NULL); |
2092 | |
2093 | if (!validated && !g_utf8_validate (str: name, max_len: -1, NULL)) |
2094 | { |
2095 | char *utf8_name; |
2096 | |
2097 | utf8_name = make_valid_utf8 (name); |
2098 | g_free (mem: name); |
2099 | |
2100 | name = utf8_name; |
2101 | } |
2102 | |
2103 | return name; |
2104 | } |
2105 | |
2106 | /** |
2107 | * gtk_recent_info_get_short_name: |
2108 | * @info: an `GtkRecentInfo` |
2109 | * |
2110 | * Computes a valid UTF-8 string that can be used as the |
2111 | * name of the item in a menu or list. |
2112 | * |
2113 | * For example, calling this function on an item that refers |
2114 | * to “file:///foo/bar.txt” will yield “bar.txt”. |
2115 | * |
2116 | * Returns: A newly-allocated string in UTF-8 encoding |
2117 | * free it with g_free() |
2118 | */ |
2119 | char * |
2120 | gtk_recent_info_get_short_name (GtkRecentInfo *info) |
2121 | { |
2122 | char *short_name; |
2123 | |
2124 | g_return_val_if_fail (info != NULL, NULL); |
2125 | |
2126 | if (info->uri == NULL) |
2127 | return NULL; |
2128 | |
2129 | short_name = get_uri_shortname_for_display (uri: info->uri); |
2130 | |
2131 | return short_name; |
2132 | } |
2133 | |
2134 | /** |
2135 | * gtk_recent_info_get_uri_display: |
2136 | * @info: a `GtkRecentInfo` |
2137 | * |
2138 | * Gets a displayable version of the resource’s URI. |
2139 | * |
2140 | * If the resource is local, it returns a local path; if the |
2141 | * resource is not local, it returns the UTF-8 encoded content |
2142 | * of [method@Gtk.RecentInfo.get_uri]. |
2143 | * |
2144 | * Returns: (nullable): a newly allocated UTF-8 string containing the |
2145 | * resource’s URI or %NULL. Use g_free() when done using it. |
2146 | */ |
2147 | char * |
2148 | gtk_recent_info_get_uri_display (GtkRecentInfo *info) |
2149 | { |
2150 | char *retval; |
2151 | |
2152 | g_return_val_if_fail (info != NULL, NULL); |
2153 | |
2154 | retval = NULL; |
2155 | if (gtk_recent_info_is_local (info)) |
2156 | { |
2157 | char *filename; |
2158 | |
2159 | filename = g_filename_from_uri (uri: info->uri, NULL, NULL); |
2160 | if (!filename) |
2161 | return NULL; |
2162 | |
2163 | retval = g_filename_to_utf8 (opsysstring: filename, len: -1, NULL, NULL, NULL); |
2164 | g_free (mem: filename); |
2165 | } |
2166 | else |
2167 | { |
2168 | retval = make_valid_utf8 (name: info->uri); |
2169 | } |
2170 | |
2171 | return retval; |
2172 | } |
2173 | |
2174 | /** |
2175 | * gtk_recent_info_get_age: |
2176 | * @info: a `GtkRecentInfo` |
2177 | * |
2178 | * Gets the number of days elapsed since the last update |
2179 | * of the resource pointed by @info. |
2180 | * |
2181 | * Returns: a positive integer containing the number of days |
2182 | * elapsed since the time this resource was last modified |
2183 | */ |
2184 | int |
2185 | gtk_recent_info_get_age (GtkRecentInfo *info) |
2186 | { |
2187 | GDateTime *now; |
2188 | |
2189 | g_return_val_if_fail (info != NULL, -1); |
2190 | |
2191 | now = g_date_time_new_now_utc (); |
2192 | |
2193 | return (int) (g_date_time_difference (end: now, begin: info->modified) / (double)G_TIME_SPAN_DAY); |
2194 | } |
2195 | |
2196 | /** |
2197 | * gtk_recent_info_get_groups: |
2198 | * @info: a `GtkRecentInfo` |
2199 | * @length: (out) (optional): return location for the number of groups returned |
2200 | * |
2201 | * Returns all groups registered for the recently used item @info. |
2202 | * |
2203 | * The array of returned group names will be %NULL terminated, so |
2204 | * length might optionally be %NULL. |
2205 | * |
2206 | * Returns: (array length=length zero-terminated=1) (transfer full): |
2207 | * a newly allocated %NULL terminated array of strings. |
2208 | * Use g_strfreev() to free it. |
2209 | */ |
2210 | char ** |
2211 | gtk_recent_info_get_groups (GtkRecentInfo *info, |
2212 | gsize *length) |
2213 | { |
2214 | char **retval; |
2215 | gsize n_groups, i; |
2216 | |
2217 | g_return_val_if_fail (info != NULL, NULL); |
2218 | |
2219 | if (!info->groups || info->n_groups == 0) |
2220 | { |
2221 | if (length) |
2222 | *length = 0; |
2223 | |
2224 | return NULL; |
2225 | } |
2226 | |
2227 | n_groups = info->n_groups; |
2228 | |
2229 | retval = g_new0 (char *, n_groups + 1); |
2230 | |
2231 | for (i = 0; i < info->n_groups; i ++) |
2232 | retval[i] = g_strdup (str: info->groups[i]); |
2233 | |
2234 | retval[i] = NULL; |
2235 | |
2236 | if (length) |
2237 | *length = info->n_groups; |
2238 | |
2239 | return retval; |
2240 | } |
2241 | |
2242 | /** |
2243 | * gtk_recent_info_has_group: |
2244 | * @info: a `GtkRecentInfo` |
2245 | * @group_name: name of a group |
2246 | * |
2247 | * Checks whether @group_name appears inside the groups |
2248 | * registered for the recently used item @info. |
2249 | * |
2250 | * Returns: %TRUE if the group was found |
2251 | */ |
2252 | gboolean |
2253 | gtk_recent_info_has_group (GtkRecentInfo *info, |
2254 | const char *group_name) |
2255 | { |
2256 | int i; |
2257 | |
2258 | g_return_val_if_fail (info != NULL, FALSE); |
2259 | g_return_val_if_fail (group_name != NULL, FALSE); |
2260 | |
2261 | if (!info->groups) |
2262 | return FALSE; |
2263 | |
2264 | for (i = 0; i < info->n_groups; i ++) |
2265 | { |
2266 | const char *g = info->groups[i]; |
2267 | |
2268 | if (strcmp (s1: g, s2: group_name) == 0) |
2269 | return TRUE; |
2270 | } |
2271 | |
2272 | return FALSE; |
2273 | } |
2274 | |
2275 | /** |
2276 | * gtk_recent_info_create_app_info: |
2277 | * @info: a `GtkRecentInfo` |
2278 | * @app_name: (nullable): the name of the application that should |
2279 | * be mapped to a `GAppInfo`; if %NULL is used then the default |
2280 | * application for the MIME type is used |
2281 | * @error: (nullable): return location for a `GError` |
2282 | * |
2283 | * Creates a `GAppInfo` for the specified `GtkRecentInfo` |
2284 | * |
2285 | * In case of error, @error will be set either with a |
2286 | * %GTK_RECENT_MANAGER_ERROR or a %G_IO_ERROR |
2287 | * |
2288 | * Returns: (nullable) (transfer full): the newly created `GAppInfo` |
2289 | */ |
2290 | GAppInfo * |
2291 | gtk_recent_info_create_app_info (GtkRecentInfo *info, |
2292 | const char *app_name, |
2293 | GError **error) |
2294 | { |
2295 | RecentAppInfo *ai; |
2296 | GAppInfo *app_info; |
2297 | GError *internal_error = NULL; |
2298 | |
2299 | g_return_val_if_fail (info != NULL, NULL); |
2300 | |
2301 | if (app_name == NULL || *app_name == '\0') |
2302 | { |
2303 | char *content_type; |
2304 | |
2305 | if (info->mime_type == NULL) |
2306 | return NULL; |
2307 | |
2308 | content_type = g_content_type_from_mime_type (mime_type: info->mime_type); |
2309 | if (content_type == NULL) |
2310 | return NULL; |
2311 | |
2312 | app_info = g_app_info_get_default_for_type (content_type, TRUE); |
2313 | g_free (mem: content_type); |
2314 | |
2315 | return app_info; |
2316 | } |
2317 | |
2318 | ai = g_hash_table_lookup (hash_table: info->apps_lookup, key: app_name); |
2319 | if (ai == NULL) |
2320 | { |
2321 | g_set_error (err: error, GTK_RECENT_MANAGER_ERROR, |
2322 | code: GTK_RECENT_MANAGER_ERROR_NOT_REGISTERED, |
2323 | _("No registered application with name “%s” for item with URI “%s” found" ), |
2324 | app_name, |
2325 | info->uri); |
2326 | return NULL; |
2327 | } |
2328 | |
2329 | internal_error = NULL; |
2330 | app_info = g_app_info_create_from_commandline (commandline: ai->exec, application_name: ai->name, |
2331 | flags: G_APP_INFO_CREATE_NONE, |
2332 | error: &internal_error); |
2333 | if (internal_error != NULL) |
2334 | { |
2335 | g_propagate_error (dest: error, src: internal_error); |
2336 | return NULL; |
2337 | } |
2338 | |
2339 | return app_info; |
2340 | } |
2341 | |
2342 | /* |
2343 | * _gtk_recent_manager_sync: |
2344 | * |
2345 | * Private function for synchronising the recent manager singleton. |
2346 | */ |
2347 | void |
2348 | _gtk_recent_manager_sync (void) |
2349 | { |
2350 | if (recent_manager_singleton) |
2351 | { |
2352 | /* force a dump of the contents of the recent manager singleton */ |
2353 | recent_manager_singleton->priv->is_dirty = TRUE; |
2354 | gtk_recent_manager_real_changed (manager: recent_manager_singleton); |
2355 | } |
2356 | } |
2357 | |