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