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